【问题标题】:Unit Test WebApi to upload image using Mock单元测试 WebApi 以使用 Mock 上传图像
【发布时间】:2015-02-09 14:52:13
【问题描述】:

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

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 的模拟??

【问题讨论】:

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


    【解决方案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 很棘手,但我过去曾成功地直接模拟它。以下是我编写的一些方法,它们使用NSubstituteHttpContext 模拟为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;
        }
    }
    

    你可能不需要我在这里嘲笑的所有东西,这只是我前一段时间写的一些代码的复制/粘贴。此外,您可能不得不在签名中使用HttpContextHttpContextBase。以下是一些可能有帮助的链接:

    【讨论】:

    • 感谢您的回复,我已经通过依赖注入更改更新了我的问题。仍然无法弄清楚如何模拟 httpContext?
    • @user2736022:我更新了一些关于模拟 HttpContext 的更多信息
    • 感谢帮助。虽然我将 HttpContextBase 传入我的控制器,而不是 HttpContext
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-31
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    相关资源
    最近更新 更多