使用 Nunit 在 ASP MVC 控制器中测试异步方法

Testing an async method in ASP MVC Controller with Nunit

我有一个 ASP.NET MVC 应用程序,其控制器具有异步方法,返回 Task<PartialViewResult> 对象并标有 async 关键字。 该方法仅以异步方式从数据库中获取数据。

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

        return PartialView(result);
    };
}

在测试期间,流只是冻结在这个位置(在 运行 时此代码运行良好):

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

这是我对这个方法的测试:

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--
}

问题:为什么我的方法中的流在测试执行期间冻结???

更新

如果我替换

,此测试将开始工作
var result = await unitOfWork.SomeRepository.GetAsync(); 

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

但我不太明白为什么会这样。有人可以解释一下吗?

测试异步方法时,您的测试方法也应该是异步的。 NUnit 可以毫无问题地处理这个问题。

[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?

测试有一些问题。

最初的示例是将阻塞调用 (.Wait()) 与导致死锁的异步调用混合在一起,从而导致挂起(死锁)。

测试应该一直转换为异步。测试运行器应该能够毫无问题地处理它。

public async Task GetExchange_GetView_OkModelIsViewModel() { ... }

接下来 GetAsync 方法的设置没有正确完成。

因为该方法未配置为 return 允许代码继续执行的已完成任务,这也会导致该任务阻塞

//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

根据测试需要,设置可以进一步简化为

//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

错误的对象也基于控制器。传递 mock 的对象。

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

然后应等待正在测试的方法的执行。

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

并且可以对结果进行断言以验证行为。

从本质上讲,问题出在测试的安排和执行方式上。不是正在测试的代码。

这是重构后的测试

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);

}