Testing file uploaded API using xunit

22 Views Asked by At

I have a class as follows:

public class FileUploadRequest
{
    public IFormFile FileUploaded { get; set; }
}

This is my Controller:

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

This is my Service:

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

Currently, I want to test with a sample file saved in the Asset folder of the Test project using xunit. This is my test:

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

}

However, I realized I couldn't create a FileUploadRequest object the same as when sending using the real API. (ContentType, ContentDisposition, Header fields are null) This causes validation at the service to fail. Is there a way to solve this problem?

1

There are 1 best solutions below

2
baterja On

Because you have to test real HTTP behavior, I wouldn't call it a unit test but instead an integration test. In the case of integration testing ASP Core controllers, you have to set up all the underlying infrastructure, just instantiating the controller's instance is not enough. Of course, it's still worth unit testing the controller but you cannot then rely on infrastructure-specific details like HTTP headers, etc.

See: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/testing?view=aspnetcore-8.0 about unit testing and: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0 about integration tests.

I would also suggest avoiding handling HTTP details and report creation in a single service. As you can see, it makes unit testing of the service not possible - because it relies on infra-like details. If it will only get the file content then unit testing will be possible. Personally, I think that issues with writing tests usually indicate not enough separated concerns in the subjects of the test.