单元测试 WebApi 使用 Mock 上传图片
Unit Test WebApi to upload image using Mock
我开始为我们所有的控制器编写单元测试并且似乎已经掌握了窍门,但现在我有点卡住了。我有以下控制器方法,我想为其编写单元测试但有点迷路。有人可以帮我指出正确的方向吗?我猜也许我需要稍微抽象一下方法,但不确定如何。
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 的模拟?
假设您正在使用依赖项注入,要使此方法可测试,您可以做的最好的事情就是将 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
设置为 return 测试 _httpContext.Request.Files.AllKeys.Any()
和 var httpPostedFile = _httpContext.Request.Files[0];
等部分所需的任何内容。
您还需要设置您的 IoC 容器(Ninject、Autofac 或任何您正在使用的容器)以将 HttpContext
/Request
的正确实例注入您的控制器。
这应该为您提供了一个良好的起点,因为深入了解更多细节需要了解您正在使用的模拟框架、IoC 容器等:)
更新
模拟 HttpContext
很棘手,但我过去曾成功地直接模拟它。以下是我编写的一些方法,它们使用 NSubstitute 将 HttpContext
模拟为 HttpContextBase
,以及模拟 HttpRequestBase
和 HttpResponseBase
属性。
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;
}
}
你可能不需要我在这里嘲笑的所有东西,这只是我刚才写的一些代码的 copy/paste。此外,您可能需要尝试在签名中使用 HttpContext
与 HttpContextBase
。以下是一些可能有帮助的链接:
我开始为我们所有的控制器编写单元测试并且似乎已经掌握了窍门,但现在我有点卡住了。我有以下控制器方法,我想为其编写单元测试但有点迷路。有人可以帮我指出正确的方向吗?我猜也许我需要稍微抽象一下方法,但不确定如何。
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 的模拟?
假设您正在使用依赖项注入,要使此方法可测试,您可以做的最好的事情就是将 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
设置为 return 测试 _httpContext.Request.Files.AllKeys.Any()
和 var httpPostedFile = _httpContext.Request.Files[0];
等部分所需的任何内容。
您还需要设置您的 IoC 容器(Ninject、Autofac 或任何您正在使用的容器)以将 HttpContext
/Request
的正确实例注入您的控制器。
这应该为您提供了一个良好的起点,因为深入了解更多细节需要了解您正在使用的模拟框架、IoC 容器等:)
更新
模拟 HttpContext
很棘手,但我过去曾成功地直接模拟它。以下是我编写的一些方法,它们使用 NSubstitute 将 HttpContext
模拟为 HttpContextBase
,以及模拟 HttpRequestBase
和 HttpResponseBase
属性。
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;
}
}
你可能不需要我在这里嘲笑的所有东西,这只是我刚才写的一些代码的 copy/paste。此外,您可能需要尝试在签名中使用 HttpContext
与 HttpContextBase
。以下是一些可能有帮助的链接: