我想在我的WebAPI控制器上“单元”测试一个方法。
此方法依赖于与其一起发送的标头。
所以
HttpContext.Current.Request.Headers["name"]
需要在方法体中有一个值。
这样做的最佳方式是什么?我以为我能够设置ControllerContext来填充HttpContext,但是无法让它工作。
我不想使用模拟框架或任何其他第三方工具,因为我的理解是WebAPI2可以很好地使用这个用例。
如果这是最好的方法,我很乐意设置HttpContext.Current。
答案 0 :(得分:20)
嗨,我可能会晚一点,但我遇到了同样的问题,我这就是我最终做的事情。
正如其他人所说,在控制器操作中使用Request.Headers而不是HttpCurrentContext,例如:
[Route("")]
[HttpGet]
public IHttpActionResult Get()
{
// The header can have multiple values, I call SingleOrDefault as I only expect 1 value.
var myHeader = Request.Headers.GetValues("X-My-Header").SingleOrDefault();
if (myHeader == "success")
{
return Ok<string>("Success!");
}
return BadRequest();
}
然后创建一个HttpControllerContext并设置这样的请求属性非常容易:
[TestMethod]
public void Get_HeaderIsValid()
{
// Arrange
var controller = new ConfigurationsController(null);
var controllerContext = new HttpControllerContext();
var request = new HttpRequestMessage();
request.Headers.Add("X-My-Header", "success");
// Don't forget these lines, if you do then the request will be null.
controllerContext.Request = request;
controller.ControllerContext = controllerContext;
// Act
var result = controller.Get() as OkNegotiatedContentResult<string>;
// Assert
Assert.IsNotNull(result);
Assert.AreEqual("Success!", result.Content);
}
希望这会有所帮助:)
P.S。别忘了将Web.Api.Core Reference添加到测试项目中:)
答案 1 :(得分:7)
有时,您对编写测试的代码几乎没有控制权。如果它已被设计为使用HttpContext.Current
,并且您不断遇到"Operation is not supported on this platform."
错误,那么这将有所帮助。
public static class NameValueCollectionExtensions
{
public static NameValueCollection AddValue(this NameValueCollection headers, string key, string value)
{
Type t = headers.GetType();
t.InvokeMember("MakeReadWrite", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
t.InvokeMember("InvalidateCachedArrays", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
t.InvokeMember("BaseAdd", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, new object[] { key, new System.Collections.ArrayList() { value } });
t.InvokeMember("MakeReadOnly", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
return headers;
}
}
在同一名称空间中使用该类,您可以添加如下标题:
HttpContext.Current.Request.Headers.AddValue("header_key", "header_value");
当然,如果你不喜欢扩展方法,你总是可以使用包装器方法。
我希望这有助于某人。
答案 2 :(得分:5)
注意:此答案适用于问题的通用标题,但在此特定情况下,用户的外部代码依赖于HttpContext.Current
无法控制的内容。如果这是你的情况,这不是你要走的路。对于大多数其他用户,仍然建议
不要依赖WebAPI中的HttpContext.Current
。一般建议避免在WebAPI中使用它,其中一个主要原因是单元可测试性。
另请注意,我正在返回IHttpActionResult
,这将使测试更加轻松。
相反,只需使用控制器成员Request.Headers
,然后就可以通过测试中的上下文对象进行设置
public class MyController : ApiController
{
public IHttpActionResult Get()
{
if (Request.Headers. /* insert your code here */)
{
// Do Something
}
}
}
public class TestClass
{
public void Test()
{
// Arrange
var controller = new MyController();
var request = new HttpRequestMessage();
request.Headers... // setup your test here
// Act
var result = controller.Get();
// Assert
// Verify here
}
}
以下是内存集成测试中完整端到端的示例(同样请注意,您需要使用整个管道中可用的Request属性而不是HttpContext.Current。此代码取自:{{3}在代码中还有一些类型的集成测试。
// Do any setup work
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("Default", "{controller}/{action}");
// Setup in memory server and client
HttpServer server = new HttpServer(config);
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync("http://localhost/" + requestUrl).Result;
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(count, response.Content.ReadAsAsync<int>().Result);
答案 3 :(得分:1)
您可以模拟HTTP请求上下文。您使用像Moq这样的模拟框架吗?这样可以轻松模拟请求标头集合。如果不使用Moq,请参阅此SO question。
答案 4 :(得分:0)
我建议。
在创建私有控制器对象时,请在那时设置这些设置。
private HomeController createHomeController()
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Key"] = "value123";
var controllerContext = new ControllerContext
{
HttpContext = httpContext
};
return new HomeController()
{
ControllerContext = controllerContext
};
}