简体   繁体   中英

Testing an async method in ASP MVC Controller with Nunit

I have an ASP.NET MVC application, with an Controller that features asynchronous methods, returning Task<PartialViewResult> object and marked with the async keyword. This method only takes the data from the database in async mode.

public async Task<PartialViewResult> SomeMethod()
{
    using (var unitOfWork = _factory.Create())
    {
        var result = await unitOfWork.SomeRepository.GetAsync();

        return PartialView(result);
    };
}

During testing, the stream just freeze in this spot (At run time this code works well):

var models = await unitOfWork.SomeRepository.GetAsync();

This is my test for this method:

public void GetExchange_GetView_OkModelIsViewModel()
{ 
    //fake Repository returns fake Data from DB
    var mockSomeRepository = new Mock<ISomeRepository>();
    mockSomeRepository.Setup(x => x.GetAsync(...).Returns(new Task<List<SomeType>>(() => new List<SomeType>()));

    //fake UoW returns fake Repository
    var mockUnitOfWork = new Mock<IUnitOfWork>();
    mockUnitOfWork.Setup(x => x.SomeRepository).Returns(mockSomeRepository.Object);

    //fake factory create fake UoW
    var fakeUnitOfWorkFactory = new Mock<UnitOfWorkFactory>();
    fakeUnitOfWorkFactory.Setup(x => x.Create()).Returns(mockUnitOfWork.Object);

    //Our controller
    var controller = new SomeController(fakeUnitOfWorkFactory);

    //Our async method
    var result = controller.SomeMethod();
    result.Wait();

    //---Assert--
}

Question : why is the stream in my method freezes during test execution???

UPDATE

This test begins to work if I replace

var result = await unitOfWork.SomeRepository.GetAsync(); 

to

var models = unitOfWork.SomeRepository.GetAsync();
models.Start();
models.Wait();
var result = models.Result;

But I don't quite understand why it works like that. Can someone explain?

When testing an async method your test method should be async as well. NUnit can handle this without issue.

[Test]
public async Task GetExchange_GetView_OkModelIsViewModel() {
    // ...

    var controller = new SomeController(fakeUnitOfWorkFactory);
    var result = await controller.SomeMethod(); // call using await

    // ...
}

why is the stream in my method freezes during test execution?

There are a few issue with the test.

The initial example was mixing a blocking call ( .Wait() ) with async calls which lead to a deadlock, hence the hang (deadlock).

The Test should be converted to be async all the way. The test runner should be able to handle that without any problems.

public async Task GetExchange_GetView_OkModelIsViewModel() { ... }

Next the setup of the GetAsync method is not done correctly.

Because the method was not configured to return a completed task that would allow the code to continue, that would also cause a block on that task

//Arrange
var fakeData = new List<SomeType>() { new SomeType() };
//fake Repository returns fake Data from DB
var mockSomeRepository = new Mock<ISomeRepository>();
mockSomeRepository
    .Setup(x => x.GetAsync())
    .Returns(Task.FromResult(fakeData)); // <-- note the correction here

Based on what is needed for the test the setup can be simplified even further to

//Arrange
var fakeData = new List<SomeType>() { new SomeType() }; 
//fake UoF returns fake Data from DB
var mockUnitOfWork = new Mock<IUnitOfWork>();
mockUnitOfWork
    .Setup(x => x.SomeRepository.GetAsync()) //<-- note the full call here
    .ReturnsAsync(fakeData); //<-- and also the use of the ReturnsAsync here

The wrong object was also being based to the controller. Pass the object of the mock.

//Our controller
var controller = new SomeController(fakeUnitOfWorkFactory.Object);

Exercising of the method under test should then be awaited.

//Our async method
var result = await controller.SomeMethod() as PartialViewResult;

and assertions can be done on the result to verify behavior.

Essentially the problems arose out of how the test was arranged and acted upon. Not the code being tested.

Here is the refactored test

public async Task GetExchange_GetView_OkModelIsViewModel() {
    //Arrange
    var fakeData = new List<SomeType>() { new SomeType() }; 
    //fake UoF returns fake Data from DB
    var mockUnitOfWork = new Mock<IUnitOfWork>();
    mockUnitOfWork
        .Setup(x => x.SomeRepository.GetAsync())
        .ReturnsAsync(fakeData);

    //fake factory create fake UoF
    var fakeUnitOfWorkFactory = new Mock<UnitOfWorkFactory>();
    fakeUnitOfWorkFactory.Setup(x => x.Create()).Returns(mockUnitOfWork.Object);

    //Our controller
    var controller = new SomeController(fakeUnitOfWorkFactory.Object);

    //Act
    //Our async method
    var result = await controller.SomeMethod() as PartialViewResult;

    //---Assert--
    result.Should().NotBeNull();

    result.Model.Should().NotBeNull();

    CollectionAssert.AreEquivalent(fakeData, result.Model as ICollection);

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM