我有一个web api终点,我想进行单元测试.我有一个自定义的SwaggerUploadFile属性,允许在swagger页面上的文件上传按钮.但是对于单元测试,我无法弄清楚如何传入文件. 对于单元测试,我使用:
对于单元测试,我使用:Xunit,Moq和Fluent Assertions
下面是我的控制器与端点:
public class MyAppController : ApiController { private readonly IMyApp _myApp; public MyAppController(IMyApp myApp) { if (myApp == null) throw new ArgumentNullException(nameof(myApp)); _myApp = myApp; } [HttpPost] [ResponseType(typeof(string))] [Route("api/myApp/UploadFile")] [SwaggerUploadFile("myFile", "Upload a .zip format file", Required = true, Type = "file")] public async Task<IHttpActionResult> UploadFile() { if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = await Request.Content.ReadAsMultipartAsync(); var bytes = await provider.Contents.First().ReadAsByteArrayAsync(); try { var retVal = _myApp.CheckAndSaveByteStreamAsync(bytes).Result; if(retVal) { return ResponseMessage( new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonConvert.SerializeObject( new WebApiResponse { Message = "File has been saved" }), Encoding.UTF8, "application/json") }); } return ResponseMessage( new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(JsonConvert.SerializeObject( new WebApiResponse { Message = "The file could not be saved" }), Encoding.UTF8, "application/json") }); } catch (Exception e) { //log error return BadRequest("Oops...something went wrong"); } } }
我到目前为止的单元测试:
[Fact] [Trait("Category", "MyAppController")] public void UploadFileTestWorks() { //Arrange _myApp.Setup(x => x.CheckAndSaveByteStreamAsync(It.IsAny<byte[]>())).ReturnsAsync(() => true); var expected = JsonConvert.SerializeObject( new WebApiResponse { Message = "The file has been saved" }); var _sut = new MyAppController(_myApp.Object); //Act var retVal = _sut.UploadFile(); var content = (ResponseMessageResult)retVal.Result; var contentResult = content.Response.Content.ReadAsStringAsync().Result; //Assert contentResult.Should().Be(expected); }
如果(!Request.Content.IsMimeMultipartContent())我们得到NullReferenceException> “{“你调用的对象是空的.”}”
最佳答案已实施:
创建了一个界面:
public interface IApiRequestProvider { Task<MultipartMemoryStreamProvider> ReadAsMultiPartAsync(); bool IsMimeMultiPartContent(); }
然后是一个实现:
public class ApiRequestProvider : ApiController, IApiRequestProvider { public Task<MultipartMemoryStreamProvider> ReadAsMultiPartAsync() { return Request.Content.ReadAsMultipartAsync(); } public bool IsMimeMultiPartContent() { return Request.Content.IsMimeMultipartContent(); } }
现在我的控制器使用构造函数注入来获取RequestProvider:
private readonly IMyApp _myApp; private readonly IApiRequestProvider _apiRequestProvider; public MyAppController(IMyApp myApp, IApiRequestProvider apiRequestProvider) { if (myApp == null) throw new ArgumentNullException(nameof(myApp)); _myApp = myApp; if (apiRequestProvider== null) throw new ArgumentNullException(nameof(apiRequestProvider)); _apiRequestProvider= apiRequestProvider; }
方法的新实现:
[HttpPost] [ResponseType(typeof(string))] [Route("api/myApp/UploadFile")] [SwaggerUploadFile("myFile", "Upload a .zip format file", Required = true, Type = "file")] public async Task<IHttpActionResult> UploadFile() { if (!_apiRequestProvider.IsMimeMultiPartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = await _apiRequestProvider.ReadAsMultiPartAsync(); var bytes = await provider.Contents.First().ReadAsByteArrayAsync(); try { var retVal = _myApp.CheckAndSaveByteStreamAsync(bytes).Result; if(retVal) { return ResponseMessage( new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonConvert.SerializeObject( new WebApiResponse { Message = "File has been saved" }), Encoding.UTF8, "application/json") }); } return ResponseMessage( new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(JsonConvert.SerializeObject( new WebApiResponse { Message = "The file could not be saved" }), Encoding.UTF8, "application/json") }); } catch (Exception e) { //log error return BadRequest("Oops...something went wrong"); } } }
我的单元测试嘲笑ApiController请求:
[Fact] [Trait("Category", "MyAppController")] public void UploadFileTestWorks() { //Arrange _apiRequestProvider = new Mock<IApiRequestProvider>(); _myApp = new Mock<IMyApp>(); MultipartMemoryStreamProvider fakeStream = new MultipartMemoryStreamProvider(); fakeStream.Contents.Add(CreateFakeMultiPartFormData()); _apiRequestProvider.Setup(x => x.IsMimeMultiPartContent()).Returns(true); _apiRequestProvider.Setup(x => x.ReadAsMultiPartAsync()).ReturnsAsync(()=>fakeStream); _myApp.Setup(x => x.CheckAndSaveByteStreamAsync(It.IsAny<byte[]>())).ReturnsAsync(() => true); var expected = JsonConvert.SerializeObject( new WebApiResponse { Message = "The file has been saved" }); var _sut = new MyAppController(_myApp.Object, _apiRequestProvider.Object); //Act var retVal = _sut.UploadFile(); var content = (ResponseMessageResult)retVal.Result; var contentResult = content.Response.Content.ReadAsStringAsync().Result; //Assert contentResult.Should().Be(expected); }
感谢@Badulake的想法
你应该在方法的逻辑中做一个更好的分离.重构您的方法,因此它不依赖于与您的Web框架相关的任何类,在本例中是Request类.您的上传代码无需了解任何相关信息.
作为提示:
var provider = await Request.Content.ReadAsMultipartAsync();
可以转换为:
var provider = IProviderExtracter.Extract(); public interface IProviderExtracter { Task<provider> Extract(); } public class RequestProviderExtracter:IProviderExtracter { public Task<provider> Extract() { return Request.Content.ReadAsMultipartAsync(); } }
在您的测试中,您可以轻松地模拟IProviderExtracter,并专注于执行代码的每个部分.
我们的想法是获得最多的解耦代码,因此您的担忧只集中在模拟您开发的类,而不是框架强制您使用的类.