Reputation: 75
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?
Upvotes: 3
Views: 2007
Reputation: 247018
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);
}
Upvotes: 0
Reputation: 62213
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
// ...
}
Upvotes: 3