gpt4 book ai didi

c# - 对上传文件的 Web API 端点进行单元测试

转载 作者:太空狗 更新时间:2023-10-30 00:37:26 31 4
gpt4 key购买 nike

我有一个要进行单元测试的 Web API 端点。我有一个自定义 SwaggerUploadFile 属性,允许在 swagger 页面上使用文件上传按钮。但是对于单元测试,我不知道如何传入文件。

对于单元测试,我使用的是:XunitMoqFluent Assertions

下面是我的 Controller 和端点:

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);
}

上面的代码失败了,因为当它到达这一行 if(!Request.Content.IsMimeMultipartContent()) 我们得到一个 NullReferenceException > "{"Object reference未设置为对象的实例。"}"

已实现最佳答案:

创建了一个接口(interface):

 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();
}
}

现在我的 Controller 使用构造函数注入(inject)来获取 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 并专注于执行代码的每个部分的工作。

想法是您获得最解耦的代码,因此您的担心只集中在模拟您开发的类上,而不是框架强制您使用的类。

关于c# - 对上传文件的 Web API 端点进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51284784/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com