Search code examples
c#asp.net-coreintegration-testingasp.net-core-webapiweb-api-testing

Integration Testing multipart/form-data c#


I have trouble trying to create an integration test for my post call that accepts a viewmodel that has amongst other values, an IFormFile, which makes this call from an application/json to a multipart/form-data

My IntegrationSetup class

protected static IFormFile GetFormFile()
        {
            byte[] bytes = Encoding.UTF8.GetBytes("test;test;");

            var file = new FormFile(
                baseStream: new MemoryStream(bytes),
                baseStreamOffset: 0,
                length: bytes.Length,
                name: "Data",
                fileName: "dummy.csv"
            )
            {
                Headers = new HeaderDictionary(),
                ContentType = "text/csv"
            };

            return file;
        } 

My Test Method

public async Task CreateAsync_ShouldReturnId()
        {
            //Arrange
            using var content = new MultipartFormDataContent();
            var stringContent = new StringContent(
                JsonConvert.SerializeObject(new CreateArticleViewmodel
                {
                    Title = "viewModel.Title",
                    SmallParagraph = "viewModel.SmallParagraph",
                    Url = "viewModel.Url",
                    Image = GetFormFile()
                }),
                Encoding.UTF8,
                "application/json");
            stringContent.Headers.Add("Content-Disposition", "form-data; name=\"json\"");
            content.Add(stringContent, "json");

            //Act
            var response = await httpClient.PostAsync($"{Url}", content);
            //Assert
            response.StatusCode.ShouldBe(HttpStatusCode.OK);
            int id = int.Parse(await response.Content.ReadAsStringAsync());
            id.ShouldBeGreaterThan(0);
        }

My Controller Method

[HttpPost]
        public async Task<IActionResult> CreateArticleAsync([FromForm] CreateArticleViewmodel viewModel)
        {

            var id = await _service.CreateAsync(viewModel).ConfigureAwait(false);
            if (id > 0)
                return Ok(id);
            return BadRequest();
        }

It throws a BadRequest without getting inside the method.


Solution

  • The way you are posting the request contents to the API, in your code, is not correct.

    When the API expects a FileInfo in the request payload, posting JSON content never works. You need to send the payload as MultipartFormData and not as JSON.

    Consider following example.

    This is a an API endpoint which expects and model with FileInfo in it as payload.

    [HttpPost]
    public IActionResult Upload([FromForm] MyData myData)
    {
        if (myData.File != null)
        {
            return Ok("File received");
        }
        else
        {
            return BadRequest("File no provided");
        }
    }
    
    public class MyData
    {
        public int Id { get; set; }
        public string Title { get; set; }
        // Below property is used for getting file from client to the server.
        public IFormFile File { get; set; }
    }
    

    This is pretty much the same API as yours.

    Following is the client code which calls the above API with file and other model properties.

    var apiURL = "http://localhost:50492/home/upload";
    const string filename = "D:\\samplefile.docx";
    
    HttpClient _client = new HttpClient();
    
    // Instead of JSON body, multipart form data will be sent as request body.
    var httpContent = new MultipartFormDataContent();
    var fileContent = new ByteArrayContent(File.ReadAllBytes(filename));
    fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
    
    // Add File property with file content
    httpContent.Add(fileContent, "file", filename);
    
    // Add id property with its value
    httpContent.Add(new StringContent("789"), "id");
    
    // Add title property with its value.
    httpContent.Add(new StringContent("Some title value"), "title");
    
    // send POST request.
    var response = await _client.PostAsync(apiURL, httpContent);
    response.EnsureSuccessStatusCode();
    var responseContent = await response.Content.ReadAsStringAsync();
    
    // output the response content to the console.
    Console.WriteLine(responseContent);
    

    The client code is running from a Console application. So when I run this, the expectation is to get File received message in the console and I am getting that message.

    console output

    Following is the screen capture of the model content at the API end while debugging it.

    API Debugging capture

    And if I am calling this API from postman, it would look like following.

    Postman capture

    I hope this will help you solve your issue.