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

时间:2014-05-07 03:36:21

标签: c# asp.net unit-testing asp.net-web-api asp.net-web-api2

我想在我的WebAPI控制器上“单元”测试一个方法。

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

所以

HttpContext.Current.Request.Headers["name"]

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

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

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

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

5 个答案:

答案 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
        };
    }