我的课程如下:
public class FileUploadRequest
{
public IFormFile FileUploaded { get; set; }
}
这是我的控制器:
public class MyController : ControllerBase
{
private readonly IMyService _service;
public MyController(IMyService service)
{
_service = service;
}
/// <summary>
/// Send a csv file to create a new report.
/// </summary>
[HttpPost]
public async Task<IActionResult> CreateReport([FromForm] FileUploadRequest inputCsvFile)
{
IdResponse response = await _service.CreateReport(inputCsvFile);
return Created("", response);
}
}
这是我的服务:
public class MyService : IMyService
{
private readonly IMyRepository _repository;
private readonly IMapper _mapper;
public MyService(
IMyRepository repository,
IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
public async Task<IdResponse> CreateReport(FileUploadRequest inputCsvFile)
{
if (inputCsvFile.FileUploaded == null)
{
throw new BusinessException(BusinessErrorCode.NO_FILE_UPLOADED);
}
if (inputCsvFile.FileUploaded.ContentType != "text/csv")
{
throw new BusinessException(BusinessErrorCode.INVALID_FILE_TYPE, inputCsvFile.FileUploaded.ContentType);
}
IEnumerable<ReportDetail> reportDetails = ParseDetailFromCsv(inputCsvFile.FileUploaded);
if(reportDetails == null || !reportDetails.Any())
{
throw new BusinessException(BusinessErrorCode.NO_VALID_DATA);
}
IEnumerable<ReportDetailDbModel> details = _mapper.Map<IEnumerable<ReportDetailDbModel>>(details);
string result = await _repository.CreateReport(inputCsvFile.FileUploaded, reportDetails);
IdResponse response = new()
{
Id = result
};
return response;
}
}
目前,我想使用 xunit 使用保存在 Test 项目的 Asset 文件夹中的示例文件进行测试。这是我的测试:
[Theory]
[InlineData("REPORT_DETAILS_TEST.csv")]
public async Task CreateReport_CreateSuccessfully(string fileName)
{
// Arrange
// Create a context with useInMemory option
using var context = new DataContext(TestHelper.CreateMockDbOptions());
var repository = new MyRepository(context);
var service = new MyService(repository, _mapper);
var controller = new MyController(service);
string exePath = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!;
string filePath = exePath + $"/Assets/{fileName}";
var formFile = new FormFile(
new MemoryStream(),
0,
new FileInfo(filePath).Length,
"ReportTestFile",
Path.GetFileName(filePath)
);
var inputFile = new FileUploadRequest { FileUploaded = formFile };
// Act
var result = await controller.CreateReport(inputFile);
// Assert
Assert.NotNull(result);
}
但是,我意识到我无法像使用真实 API 发送时一样创建 FileUploadRequest 对象。 (ContentType、ContentDisposition、Header 字段为空)这会导致服务验证失败。有办法解决这个问题吗?
因为您必须测试真实的 HTTP 行为,所以我不会将其称为 unit 测试,而是称为 integration 测试。在集成测试 ASP Core 控制器的情况下,您必须设置所有底层基础设施,仅实例化控制器的实例是不够的。当然,仍然值得对控制器进行单元测试,但您不能依赖特定于基础设施的详细信息,例如 HTTP 标头等。
有关单元测试的信息,请参阅:https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/testing?view=aspnetcore-8.0 以及: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0 关于集成测试。
我还建议避免在单个服务中处理 HTTP 详细信息和报告创建。正如您所看到的,它使得服务的单元测试变得不可能 - 因为它依赖于类似基础设施的细节。如果它只获取文件内容,则可以进行单元测试。就我个人而言,我认为编写测试的问题通常表明测试主题中没有足够的独立关注点。