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

时间:2017-03-03 10:38:41

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

我有一个ASP.NET MVC应用程序,其中一个Controller具有异步方法,返回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;

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

2 个答案:

答案 0 :(得分:3)

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

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

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

    // ...
}

答案 1 :(得分:0)

  

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

测试存在一些问题。

最初的例子是将阻塞调用(.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);

}
相关问题