Yordanka Toteva
Yordanka Toteva

Reputation: 65

Unit testing file upload fails

I have an action that inserts a file in a database. It works fine. But the unit test fails when the insert is successful. The RedirectToRouteResult is null. This is the code for the action:

[HttpPost] public ActionResult FileUpload(FileUploadViewModel model) { try { if (ModelState.IsValid) { var uploadFile = new byte[model.File.InputStream.Length]; model.File.InputStream.Read(uploadFile, 0, uploadFile.Length); //var file = new FilesUpload(); var fileExt = Path.GetExtension(model.File.FileName); if (fileExt == ".txt" || fileExt == ".docx") { //file.FileName = model.File.FileName; //file.FileSize = model.File.ContentLength; //file.ContentType = fileExt; //file.FileData = uploadFile; //file.UploadedOn = DateTime.Now; //var user = _userRepository.GetUserId(User.Identity.Name); //file.UserId = user.UserId; bool result = _userRepository.UploadFile(model, User.Identity.Name, fileExt, uploadFile); //file.User = (from u in _context.Users where u.UserId == 29 select u).FirstOrDefault(); //_context.FilesUploads.Add(file); //int result = _context.SaveChanges(); if (result) { return RedirectToAction("Index"); } else { ModelState.AddModelError("", "An error occured"); return View(model); } } else { ModelState.AddModelError("", "You can upload only files with extensions .txt and .docx"); } } return View(model); } catch (Exception ex) { throw(ex); } }

And this is the code for the unit test:

    private SuperUserController _controller;
    private Mock<IUserRepository> _repositoryMock;
    private Mock<IIntrinsicObjects> _intrinsicMock;
    private Mock<ControllerContext> _contextMock;
    private string username = "[email protected]";
    private FileUploadViewModel _fileUpload;

    [SetUp]
    public void Setup()
    {
        _fileUpload = new FileUploadViewModel();
        _repositoryMock = new Mock<IUserRepository>();
        _intrinsicMock = new Mock<IIntrinsicObjects>();
        _contextMock = new Mock<ControllerContext>();
        _contextMock.SetupGet(c => c.HttpContext.User.Identity.Name).Returns(username);
        _contextMock.SetupGet(c => c.HttpContext.User.Identity.IsAuthenticated).Returns(true);
        _controller = new SuperUserController(_repositoryMock.Object, _intrinsicMock.Object);
        _controller.ControllerContext = _contextMock.Object;
    }


[Test]
    public void FileUpload_ShouldRedirectToIndexOnSuccess()
    {
        string filePath = Path.GetFullPath(@"C:\Users\Test\Desktop\test.txt");
        var fileStream = new FileStream(filePath, FileMode.Open);
        var fileExt = Path.GetExtension(filePath);
        var fileToUpload = new Mock<HttpPostedFileBase>();
        fileToUpload.Setup(f => f.ContentLength).Returns(10);
        fileToUpload.Setup(f => f.FileName).Returns("test.txt");
        fileToUpload.Setup(f => f.ContentType).Returns(fileExt);
        fileToUpload.Setup(f => f.InputStream).Returns(fileStream);
        //var model = new FileUploadViewModel();
        _fileUpload.File = fileToUpload.Object;
        var fileData = new byte[fileToUpload.Object.InputStream.Length];
        _repositoryMock.Setup(m => m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name, fileToUpload.Object.ContentType, fileData)).Returns(true);
        var result = _controller.FileUpload(_fileUpload) as RedirectToRouteResult;
        Assert.IsNotNull(result);
    }

The problem is that UploadFile always returns false.

I will be very grateful if somebody helps me.

Upvotes: 1

Views: 664

Answers (2)

Daniel J.G.
Daniel J.G.

Reputation: 35042

Your controller action creates a new array with the file data:

var uploadFile = new byte[model.File.InputStream.Length];

That is never going to match the fileData argument set on the UploadFile of your _repositoryMock:

_repositoryMock.Setup(m => m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name, fileToUpload.Object.ContentType, fileData)).Returns(true);

By default the array will match by reference, not if the contents are the same. As the controller creates a new instance, uploadFile is a different reference than fileData.

You want to setup moq so when the expected data array is submitted your UploadFile function returns true. For that you can use an custom matcher that matches all elements in an enumerable:

public T[] MatchCollection<T>(T[] expectation)
{
    //This checks all elements in input are find in destination, regardless of order or duplicates
    return Match.Create<T[]>(inputCollection => (expectation.All((i) => inputCollection.Contains(i))));

    //This will check the arrays are exactly the same
    return Match.Create<T[]>(inputCollection =>  StructuralComparisons.StructuralEqualityComparer.Equals(inputCollection, expectation) );
}

And setup your mock using that matcher for the data array:

_repositoryMock.Setup(m => m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name, fileToUpload.Object.ContentType, MatchCollection(fileData))).Returns(true);

Hope it helps!

EDIT


I have tried with the following test based on your code that passes a valid array data to the mock setup and doesn´t need to actually read a file in the unit test:

public void FileUpload_ShouldRedirectToIndexOnSuccess()
{
    var fileData = UTF8Encoding.UTF8.GetBytes("This is a test");
    var fileStream = new MemoryStream(fileData);

    var fileToUpload = new Mock<HttpPostedFileBase>();
    fileToUpload.Setup(f => f.ContentLength).Returns((int)fileStream.Length);
    fileToUpload.Setup(f => f.FileName).Returns("test.txt");
    fileToUpload.Setup(f => f.ContentType).Returns("txt");
    fileToUpload.Setup(f => f.InputStream).Returns(fileStream);
    _fileUpload.File = fileToUpload.Object;            

    _repositoryMock
        .Setup(m => m.UploadFile(_fileUpload, username, ".txt", MatchCollection(fileData)))
        .Returns(true);

    var result = _controller.FileUpload(_fileUpload) as RedirectToRouteResult;
    Assert.IsNotNull(result);
}

Upvotes: 1

Yordanka Toteva
Yordanka Toteva

Reputation: 65

This is the final version of the test:

[Test]
    public void FileUpload_ShouldRedirectToIndexOnSuccess()
    {
        string filePath = Path.GetFullPath(@"C:\Users\Yordanka\Desktop\test.txt");
        var fileStream = new FileStream(filePath, FileMode.Open);
        var fileExt = Path.GetExtension(filePath);
        var testarray = new byte[414];
        var fileToUpload = new Mock<HttpPostedFileBase>();
        fileToUpload.Setup(f => f.ContentLength).Returns(414);
        fileToUpload.Setup(f => f.FileName).Returns("test.txt");
        fileToUpload.Setup(f => f.ContentType).Returns(fileExt);
        fileToUpload.Setup(f => f.InputStream).Returns(fileStream);
        fileToUpload.Setup(f => f.InputStream.Length).Returns(testarray.Length);
        _fileUpload.File = fileToUpload.Object;
        var fileData = new byte[414];
        _fileUpload.File.InputStream.Read(fileData, 0, fileData.Length);
        _repositoryMock.Setup(
            m =>
                m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name,
                    fileToUpload.Object.ContentType, MatchCollection(fileData))).Returns(true);
        var result = _controller.FileUpload(_fileUpload) as RedirectToRouteResult;
        Assert.IsNotNull(result);
        Assert.AreEqual("Index", result.RouteValues["Action"]);
        Assert.AreEqual("SuperUser", result.RouteValues["Controller"]);

    }

Once again, thank you Daniel J.G.!!!

Upvotes: 0

Related Questions