'xunit test for IFormFile field in Asp.net Core

I have an Asp.net Core method with below definition.

[HttpPost]
public IActionResult Upload(IFormFile file)
{
    if (file == null || file.Length == 0)
        throw new Exception("file should not be null");

    var originalFileName = ContentDispositionHeaderValue
        .Parse(file.ContentDisposition)
        .FileName
        .Trim('"');

    file.SaveAs("your_file_full_address");
}

I want to create XUnit Test for this function, how could I mock IFormFile?

Update:

Controller:

[HttpPost]
public async Task<ActionResult> Post(IFormFile file)
{

    var path = Path.Combine(@"E:\path", file.FileName);

    using (var stream = new FileStream(path, FileMode.Create))
    {
        await file.CopyToAsync(stream);
    }
    return Ok();
}

Xunit Test

[Fact]
public async void Test1()
{
    var file = new Mock<IFormFile>();
    var sourceImg = File.OpenRead(@"source image path");
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(sourceImg);
    writer.Flush();
    stream.Position = 0;
    var fileName = "QQ.png";
    file.Setup(f => f.OpenReadStream()).Returns(stream);
    file.Setup(f => f.FileName).Returns(fileName);
    file.Setup(f => f.Length).Returns(stream.Length);

    var controller = new ValuesController();
    var inputFile = file.Object;

    var result = await controller.Post(inputFile);

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

But, I got empty image in the target path.



Solution 1:[1]

When testing with IFormFile dependencies, mock the minimal necessary members to exercise the test. In the Controller above FileName property and CopyToAsync method are used. Those should be setup for the test.

public async Task Test1() {
    // Arrange.
    var file = new Mock<IFormFile>();
    var sourceImg = File.OpenRead(@"source image path");
    var ms = new MemoryStream();
    var writer = new StreamWriter(ms);
    writer.Write(sourceImg);
    writer.Flush();
    ms.Position = 0;
    var fileName = "QQ.png";
    file.Setup(f => f.FileName).Returns(fileName).Verifiable();
    file.Setup(_ => _.CopyToAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
        .Returns((Stream stream, CancellationToken token) => ms.CopyToAsync(stream))
        .Verifiable();

    var controller = new ValuesController();
    var inputFile = file.Object;

    // Act.
    var result = await controller.Post(inputFile);

    //Assert.
    file.Verify();
    //...
}

Though mentioned in the comments that the question is just a demo, the tight coupling to the file system should be abstracted to allow for better flexibility

Solution 2:[2]

you can create an actual instance just like that...

bytes[] filebytes = Encoding.UTF8.GetBytes("dummy image");
IFormFile file = new FormFile(new MemoryStream(filebytes), 0, filebytes.Length, "Data", "image.png");

Solution 3:[3]

I had to unit test proper jpeg file upload with correct User, so:

private ControllerContext RequestWithFile()
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers.Add("Content-Type", "multipart/form-data");

var sampleImagePath = host.WebRootPath + SampleImagePath; //path to correct image    
var b1 = new Bitmap(sampleImagePath).ToByteArray(ImageFormat.Jpeg);

MemoryStream ms = new MemoryStream(b1);    
var fileMock = new Mock<IFormFile>();

fileMock.Setup(f => f.Name).Returns("files");
fileMock.Setup(f => f.FileName).Returns("sampleImage.jpg");
fileMock.Setup(f => f.Length).Returns(b1.Length);
fileMock.Setup(_ => _.CopyToAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns((Stream stream, CancellationToken token) => ms.CopyToAsync(stream))
.Verifiable();              

string val = "form-data; name=";      

val += "\\";
val += "\"";
val += "files";
val += "\\";
val += "\"";
val += "; filename=";
val += "\\";
val += "\"";
val += "sampleImage.jpg";
val += "\\";
val += "\"";


fileMock.Setup(f => f.ContentType).Returns(val);
fileMock.Setup(f => f.ContentDisposition).Returns("image/jpeg");      


httpContext.User = ClaimsPrincipal; //user part, you might not need it
httpContext.Request.Form = 
new FormCollection(new Dictionary<string, StringValues>(), new FormFileCollection { fileMock.Object });
var actx = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor());

return new ControllerContext(actx);
}

Then in test:

[Fact]
public async void TestUploadFile_byFile()
{
var amountOfPosts = _dbContext.Posts.Count();
var amountOfPics = _dbContext.SmallImages.Count();

sut.ControllerContext = RequestWithFile();
var ret = await sut.UploadNewDogePOST(new Doge.Areas.User.Models.UploadDoge());

var amountOfPosts2 = _dbContext.Posts.Count();
var amountOfPics2 = _dbContext.SmallImages.Count();

Assert.True(amountOfPosts < amountOfPosts2);
Assert.True(amountOfPics < amountOfPics2);

var lastImage = _dbContext.SmallImages.Include(im => im.DogeBigImage).Last();
var sampleImagePath = host.WebRootPath + SampleImagePath;
var b1 = new Bitmap(sampleImagePath).ToByteArray(ImageFormat.Jpeg);

Assert.True(b1.Length == lastImage.DogeBigImage.Image.Length);

Assert.IsType<RedirectToActionResult>(ret);
Assert.Equal("Index", ((RedirectToActionResult)ret).ActionName);

}

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 John Nyingi
Solution 3 Michael Snytko