'How to mock an IFormFile for a unit/integration test in ASP.NET Core?

I want to write tests for uploading of files in ASP.NET Core but can't seem to find a nice way to mock/instantiate an object derived from IFormFile.

Any suggestions on how to do this?



Solution 1:[1]

Assuming you have a Controller like..

public class MyController : Controller {
    public Task<IActionResult> UploadSingle(IFormFile file) {...}
}

...where the IFormFile.OpenReadStream() is accessed with the method under test.

As of ASP.NET Core 3.0, use an instance of the FormFile Class which is now the default implementation of IFormFile.

Here is an example of the same test above using FormFile class

[TestClass]
public class IFormFileUnitTests {
    [TestMethod]
    public async Task Should_Upload_Single_File() {
        //Arrange
       
        //Setup mock file using a memory stream
        var content = "Hello World from a Fake File";
        var fileName = "test.pdf";
        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);
        writer.Write(content);
        writer.Flush();
        stream.Position = 0;

        //create FormFile with desired data
        IFormFile file = new FormFile(stream, 0, stream.Length, "id_from_form", fileName);
        
        MyController sut = new MyController();
        
        //Act
        var result = await sut.UploadSingle(file);

        //Assert
        Assert.IsInstanceOfType(result, typeof(IActionResult));
    }
}

Before the introduction of the FormFile Class or in in cases where an instance is not needed you can create a test using Moq mocking framework to simulate the stream data.

[TestClass]
public class IFormFileUnitTests {
    [TestMethod]
    public async Task Should_Upload_Single_File() {
        //Arrange
        var fileMock = new Mock<IFormFile>();
        //Setup mock file using a memory stream
        var content = "Hello World from a Fake File";
        var fileName = "test.pdf";
        var ms = new MemoryStream();
        var writer = new StreamWriter(ms);
        writer.Write(content);
        writer.Flush();
        ms.Position = 0;
        fileMock.Setup(_ => _.OpenReadStream()).Returns(ms);
        fileMock.Setup(_ => _.FileName).Returns(fileName);
        fileMock.Setup(_ => _.Length).Returns(ms.Length);

        var sut = new MyController();
        var file = fileMock.Object;

        //Act
        var result = await sut.UploadSingle(file);

        //Assert
        Assert.IsInstanceOfType(result, typeof(IActionResult));
    }
}

Solution 2:[2]

Easier would be to create an actual in-memory instance

 var bytes = Encoding.UTF8.GetBytes("This is a dummy file");
 IFormFile file = new FormFile(new MemoryStream(bytes), 0, bytes.Length, "Data", "dummy.txt");

Solution 3:[3]

Adding on harishr's answer with a set lenght (which was what I needed for my Blob.Upload() to work).

private IFormFile CreateTestFormFile(string fileName, string content)
{
    byte[] bytes = Encoding.UTF8.GetBytes(content);

    return new FormFile(
        baseStream: new MemoryStream(bytes),
        baseStreamOffset: 0,
        length: bytes.Length,
        name: "Data",
        fileName: fileName
    );
}

Solution 4:[4]

I could not mock the IFormFile, because I had problem with the Stream, so I end up with creating instance of FormFile. If you using validators etc. for the file be aware you need to set content type.

        private IFormFile GetFileMock(string contentType, string content)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(content);

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

            return file;
        }
        

to call

 GetFileMock("text/csv", "test;test;")

Solution 5:[5]

private static IFormFileCollection GetFormFileCollection()
{
    var filesFolder = $"{AppDomain.CurrentDomain.SetupInformation.ApplicationBase}UploadFiles\\";
    List<string> filesPathsListToUpload = new List<string>();
    filesPathsListToUpload.Add($"{filesFolder}UploadFile1.png");
    filesPathsListToUpload.Add($"{filesFolder}UploadFile2.jpg");
    filesPathsListToUpload.Add($"{filesFolder}UploadFile3.bmp");

    FormFileCollection filesCollection = new FormFileCollection();
    foreach (var filePath in filesPathsListToUpload)
    {
        var stream = File.OpenRead(filePath);
        IFormFile file = new FormFile(stream, 0, stream.Length, "files", Path.GetFileName(filePath))
        {
            Headers = new HeaderDictionary(),
            ContentType = filePath.Split('.')[1] == "jpg" ? "image/jpeg"
                : filePath.Split('.')[1] == "png" ? "image/png"
                : "image/bmp",
        };
        
        filesCollection.Add(file);
    }

    var httpContext = new DefaultHttpContext();
    httpContext.Request.Headers.Add("Content-Type", "multipart/form-data");
    httpContext.Request.Form = new FormCollection(new Dictionary<string, StringValues>(), filesCollection);

    return httpContext.Request.Form.Files;
}

Solution 6:[6]

I am using this nippet for my intergration tests

public static MultipartFormDataContent CreateTestFile(string fileName, string fileContent)
        {
            var content = new MultipartFormDataContent();
            content.Add(new ByteArrayContent(Encoding.UTF8.GetBytes(fileContent)), "files", fileName);
            return content;
        }

And thats how I use it with my integration tests

[Fact]
        public async Task UploadDocument_ReturnsOk()
        {
            // Arrange
            var provider = TestClaimsProvider.WithUserClaims();
            var client = _factory.CreateClientWithTestAuth(provider);

            // Act
            HttpResponseMessage? resultResponseMessage = await client.PutAsync($"/api/Documents",
                WebApiExtensions.CreateTestFile("test.csv", "content"));

            // Assert
            resultResponseMessage.ShouldHave200Code();
        }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 dawid debinski
Solution 5 Chris
Solution 6 Lonli-Lokli