使用标头值对WebAPI2控制器方法进行单元测试

Ev.*_*Ev. 7 c# asp.net unit-testing asp.net-web-api asp.net-web-api2

我想在我的WebAPI控制器上"单元"测试一个方法.

此方法依赖于与其一起发送的标头.

所以

HttpContext.Current.Request.Headers["name"]
Run Code Online (Sandbox Code Playgroud)

需要在方法体中有一个值.

这样做的最佳方式是什么?我以为我能够设置ControllerContext来填充HttpContext,但无法让它工作.

我不想使用模拟框架或任何其他第三方工具,因为我的理解是WebAPI2可以很好地使用这个用例.

如果这是最好的方法,我很乐意设置HttpContext.Current.

小智 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();
    }
Run Code Online (Sandbox Code Playgroud)

然后很容易创建一个HttpControllerContext并像这样设置request属性:

[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);
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助 :)

Ps别忘了将Web.Api.Core参考添加到测试项目:)


小智 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在同一名称空间中使用该类,您可以添加如下标题:

HttpContext.Current.Request.Headers.AddValue("header_key", "header_value");
Run Code Online (Sandbox Code Playgroud)

当然,如果您不喜欢扩展方法,则可以始终使用包装器方法.

我希望这可以帮助别人.


Yis*_*zer 5

注意:此答案适用于问题的通用标题,但在此特定情况下,用户具有依赖HttpContext.Current于其无法控制的外部代码.如果这是你的情况,这不是你要走的路.对于大多数其他用户,仍然建议这样做

不要依赖HttpContext.Current于WebAPI.一般建议避免在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
     }
 }
Run Code Online (Sandbox Code Playgroud)

下面是内存集成测试中完整端到端的示例(再次注意,您需要使用整个管道中可用的Request属性而不是HttpContext.Current.此代码取自:WebAPI测试还有一些样式代码中的集成测试.

// 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);
Run Code Online (Sandbox Code Playgroud)