单元测试Web Api使用Mock上传图像

时间:2015-02-09 14:52:14

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

我开始为我们所有的控制器编写单元测试,似乎已经掌握了它,但现在我有点卡住了。我有以下控制器方法,我想写一个单元测试,但有点丢失。有人可以帮助并指出我正确的方向。我猜也许我需要稍微抽象一下这个方法,但不确定如何。

public async Task<IHttpActionResult> PostAsync()
{
    if (HttpContext.Current.Request.Files.AllKeys.Any())
    {
        // Get the uploaded image from the Files collection
        var httpPostedFile = HttpContext.Current.Request.Files[0];

        if (httpPostedFile != null)
        {
            // Validate the uploaded image, by only accepting certain file types and sizes
            var validExtensions = new List<string>
            {
                ".JPG", ".JPE", ".BMP", ".GIF", ".PNG"
            };

            if (!validExtensions.Contains(Path.GetExtension(httpPostedFile.FileName).ToUpperInvariant()))
            {
                return BadRequest();
            }
            else if (httpPostedFile.ContentLength > 2097152)
            {
                // file is over 2mb in size
                return BadRequest();
            }

            // create a new image
            var entity = new Image
            {
                Name = httpPostedFile.FileName,
                Size = httpPostedFile.ContentLength,
                Data = new ImageData
                {
                    Content = new byte[httpPostedFile.ContentLength]   
                }
            };

            await httpPostedFile.InputStream.ReadAsync(entity.Data.Content, 0, httpPostedFile.ContentLength);

            await _service.AddAsync(entity);

            return Created<ImageModel>(Request.RequestUri, Mapper.Map<ImageModel>(entity));
        }
    }

    return BadRequest();

}

编辑:

抱歉,我完全忘了包含依赖注入代码。我正在使用SimpleInjector。

所以我现在已经添加了它

// return current httpContext
container.RegisterPerWebRequest<HttpContext>(() => HttpContext.Current);

我还不能测试,因为我仍然无法弄清楚如何模拟httpContext。我的控制器现在是这样创建的

private IImageService _service;
private HttpContext _httpContext;

public ImageController(IImageService service, HttpContext httpContext)
{
    _service = service;
    _httpContext = httpContext;
}

我已将HttpContext.Current更改为_httpContext。

但是我怎样才能创建一个HttpContext的模拟器?

1 个答案:

答案 0 :(得分:1)

假设您正在使用依赖注入,那么使此方法可测试的最佳方法是将HttpContext.Current,甚至是Request作为依赖项注入控制器。

public class MyController
{
    private readonly IMyService _service;
    private readonly HttpContext _httpContext;

    public MyController(IMyService service, HttpContext httpContext)
    {
        _service = service;
        _httpContext = httpContext;
    }

    public async Task<IHttpActionResult> PostAsync()
    {
        // action method content
    }
}

然后更新您的操作方法以使用_httpContext而不是静态HttpContext.Current。这样做可以让你在测试中创建一个模拟HttpContext并将其传递给你的控制器,让你控制它。

在此处,设置模拟HttpContext,以便为_httpContext.Request.Files.AllKeys.Any()var httpPostedFile = _httpContext.Request.Files[0];等部分提供测试所需的任何内容。

您还需要设置IoC容器(Ninject,Autofac或您正在使用的任何容器),以便将正确的HttpContext / Request实例注入您的控制器。

这应该为您提供一个好的起点,因为进入更多细节需要了解您正在使用的模拟框架,IoC容器等:)


更新

嘲弄HttpContext很棘手,但我过去直接嘲笑过它。以下是我编写的一些使用NSubstitute模拟HttpContext作为HttpContextBase的方法,以及模拟HttpRequestBaseHttpResponseBase属性。

public static class MockHttpObjectBuilder
{
    public static HttpContextBase GetMockHttpContext()
    {
        return GetMockHttpContext("~/");
    }

    public static HttpContextBase GetMockHttpContext(string url, string httpMethod = "GET")
    {
        var context = Substitute.For<HttpContextBase>();

        var req = GetMockHttpRequest(url, httpMethod);
        req.RequestContext.Returns(new RequestContext(context, new RouteData()));
        context.Request.Returns(req);

        var res = GetMockHttpResponse();
        context.Response.Returns(res);

        return context;
    }

    public static HttpRequestBase GetMockHttpRequest()
    {
        return GetMockHttpRequest("~/");
    }

    public static HttpRequestBase GetMockHttpRequest(string url, string httpMethod = "GET")
    {
        var req = Substitute.For<HttpRequestBase>();
        req.ApplicationPath.Returns("/");
        req.Headers.Returns(new NameValueCollection {{"X-Cluster-Client-Ip", "1.2.3.4"}});
        req.ServerVariables.Returns(new NameValueCollection());
        req.AppRelativeCurrentExecutionFilePath.Returns(url);
        req.HttpMethod.Returns(httpMethod);
        req.Url.Returns(new Uri("example.com/" + url.TrimStart('~')));
        return req;
    }

    public static HttpResponseBase GetMockHttpResponse()
    {
        var res = Substitute.For<HttpResponseBase>();
        res.ApplyAppPathModifier(Arg.Any<string>()).Returns(x => x[0]);
        return res;
    }
}

你可能不需要我在这里嘲笑的所有东西,这只是我刚才写的一些代码的复制/粘贴。此外,您可能需要在签名中使用HttpContext vs HttpContextBase。以下是一些可能有用的链接: