【问题标题】:Testing an async method in ASP MVC Controller with Nunit使用 Nunit 在 ASP MVC 控制器中测试异步方法
【发布时间】:2017-07-23 10:22:18
【问题描述】:

我有一个 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;

但我不太明白为什么会这样。谁能解释一下?

【问题讨论】:

  • 最初的例子是将阻塞调用(.Result.Wait())与异步调用混合,这会导致死锁,从而导致挂起。使整个测试和被测代码一直异步。

标签: c# asp.net-mvc unit-testing nunit moq


【解决方案1】:

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

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

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

    // ...
}

【讨论】:

  • @TitovS.P. - 你可以说得更详细点吗?什么不起作用以及如何?你有例外吗?会死机吗?代码是否发出编译时错误?
  • 程序只是在这个命令处冻结。她可能正在等待线程的终止。
  • @TitovS.P. - 你能分享SomeRepository.GetAsync()的代码吗?莫非执行没有被冻结,但是数据量很大?
  • 如果数据量很大,任何形式都行不通。但这仅适用于单元测试和 GetAsync() 之前的等待。
  • @TitovS.P. - 真的....如果您使用上面的代码进行模拟并将您的测试转换为async 并在控制器上使用await,我真的看不出它会挂起的原因。您可以调试并查看调用堆栈指向托管线程的位置吗?有某种僵局,但我不能确定在哪里,它可能是你在嘲笑你的DbSet....
【解决方案2】:

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

测试有一些问题。

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

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

public async Task GetExchange_GetView_OkModelIsViewModel() { ... }

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

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

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

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

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

}

【讨论】:

  • 现在可以工作了!!这是非常有用的信息,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-22
  • 1970-01-01
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
  • 2014-07-14
相关资源
最近更新 更多