7

I have a web api end point that i want to unit test. I have a custom SwaggerUploadFile attribute that allows a file upload button on the swagger page. But for unit testing I cant figure out how to pass in a file.

For unit testing I am using: Xunit, Moq and Fluent Assertions

Below is my controller with the endpoint:

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

Unit test I have so far:

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

The above fails as when it hits this line if(!Request.Content.IsMimeMultipartContent()) we get a NullReferenceException > "{"Object reference not set to an instance of an object."}"

Best Answer Implemented:

Created an interface:

 public interface IApiRequestProvider
    {
        Task<MultipartMemoryStreamProvider> ReadAsMultiPartAsync();

        bool IsMimeMultiPartContent();
    }

Then an implementation:

public class ApiRequestProvider : ApiController, IApiRequestProvider
    {       
        public Task<MultipartMemoryStreamProvider> ReadAsMultiPartAsync()
        {
            return Request.Content.ReadAsMultipartAsync();
        }
        public bool IsMimeMultiPartContent()
        {
            return Request.Content.IsMimeMultipartContent();
        }
    }

Now my controller uses constructor injection to get the 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;
        }

New implementation on method:

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

And my unit test that mocks the ApiController Request:

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

Thanks to @Badulake for the idea

3
  • 1
    Did this actually work for you? I used the same setup return Request.Content.IsMimeMultipartContent(); In the ApiRequestProvider throws null reference Commented Apr 18, 2019 at 19:59
  • yes this worked for me, did you get it working? Commented Apr 24, 2019 at 8:19
  • Didn't work for me either, got a null reference exception too. I'm presuming because 'Request' is null in the interface. Commented Jan 21, 2022 at 17:35

2 Answers 2

6

You should do a better separation in the logic of the method. Refactor your method so it does not depends on any class related to your web framework , in this case the Request class. Your upload code does not need to know anything about it. As a hint:

var provider = await Request.Content.ReadAsMultipartAsync();

could be transformed to:

var provider = IProviderExtracter.Extract();

public interface IProviderExtracter
{
    Task<provider> Extract();
}

public class RequestProviderExtracter:IProviderExtracter
{
    public Task<provider> Extract()
    { 
      return Request.Content.ReadAsMultipartAsync();
    }
}

In your tests you can easely mock IProviderExtracter and focus in which work is doing each part of your code.

The idea is you get the most decoupled code so your worries are focused only in mocking the classes you have developed, no the ones the framework forces you to use.

Sign up to request clarification or add additional context in comments.

5 Comments

thanks for your answer, I managed it to solve my unit test issue, but i will experiment implementing your abstraction
@JohnChris as much time you spend unit testing you will notice that the effort worths the result. My answer is just an example, you will have to choose the parts you want your code to be abstracted of
yeah i agree, ill try it out now, i think it's better than my current solution if i can abstract it to an interface and mock it
@JohnChris go ahead and come back in some days to tell us your feelings ;) I will get the beers chilled
worked like a charm thanks for the help, i posted my implementation below my question
3

The below was how I initially solved it but after Badulake's answer i implemented that where i abstracted the api request to an interface/class and Mocked it out with Moq. I edited my question and put the best implementation there, but i left this answer here for people who dont want to go to the trouble of mocking it

I used part of this guide but I made a simpler solution:

New unit test:

    [Fact]
    [Trait("Category", "MyAppController")]
    public void UploadFileTestWorks()
    {
        //Arrange
       var multiPartContent = CreateFakeMultiPartFormData();
        _myApp.Setup(x => x.CheckAndSaveByteStreamAsync(It.IsAny<byte[]>())).ReturnsAsync(() => true);
        var expected = JsonConvert.SerializeObject(
        new WebApiResponse
        {
            Message = "The file has been saved"
        });

        
        _sut = new MyAppController(_myApp.Object);

        //Sets a controller request message content to 
        _sut.Request = new HttpRequestMessage()
        {
            Method = HttpMethod.Post,
            Content = multiPartContent
        };
        //Act
        var retVal = _sut.UploadFile();
        var content = (ResponseMessageResult)retVal.Result;
        var contentResult = content.Response.Content.ReadAsStringAsync().Result;
        //Assert
        contentResult.Should().Be(expected); 
    }
    

Private support method:

    private static MultipartFormDataContent CreateFakeMultiPartFormData()
    {
        byte[] data = { 1, 2, 3, 4, 5 };
        ByteArrayContent byteContent = new ByteArrayContent(data);
        StringContent stringContent = new StringContent(
            "blah blah",
            System.Text.Encoding.UTF8);

        MultipartFormDataContent multipartContent = new MultipartFormDataContent { byteContent, stringContent };
        return multipartContent;
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.