在任务中使用异步等待的测试方法

时间:2017-09-26 15:15:41

标签: c# async-await nunit

我有这个代码

public class ClassToTest
{
    private readonly IRepository repository;

    public ClassToTest(DI GOES HERE){...}

    public DoSomething() 
    {
        Task.Run(async () => {
            //some code
            repository.ExecuteAsync();
        }
    }
}

public class Repository : IRepository
{
    public Task ExecuteAsync()
    {
        using (var connection = new SqlConnection(DbConfiguration.DatabaseConnection))
        {
            return connection.ExecuteAsync(storedProcedure, parameters, commandType: CommandType.StoredProcedure, commandTimeout: Configuration.TransactionTimeout);
        }
    }
}

[Test]
public void TestMethod()
{
    var repository = new Mock<IRepository>;
    var classToTest =  new ClassToTest();

    classToTest.DoSomething();

    repository.Veryfy(p => p.ExecuteAsync(), Times.Once());
}

此消息的测试失败

模拟一次的预期调用,但是是0次:p =&gt; p.ExecuteAsync()

有谁知道为什么?

由于

1 个答案:

答案 0 :(得分:5)

正如其他人所提到的那样,因为您正在调用Task.Run而不是等待响应,所以单元测试可能会在后台任务开始之前完成,因此Moq验证失败

此外,您的代码不会按原样编译 - 当在StackOverflow上询问Q时,请确保提供完整的,可编译的MVP。

特别重要的是您要测试的代码中的错误。 Repository.ExecuteAsyncconnection.ExecuteAsync范围内调用using,但尚未等待。这意味着连接将be disposed before the task completes。您需要将方法更改为asyncawait调用以推迟处理连接。

包装器方法DoSomething方法不应该使用Task.Run(),但是因为它adds no value到存储库任务,它不需要重复async / return await 1}},或者。

调用者(在本例中为您的单元测试)可以等待DoSomething(或者如果调用者真的想要在不等待任务的情况下进行进一步处理,则将其留给调用者来决定。至少这个方式,调用者获取任务的句柄,以检查完成情况。

代码的最终状态看起来可能更像:

public class ClassToTest
{
    private readonly IRepository _repository;

    public ClassToTest(IRepository repository)
    {
       _repository = repository;
    }

    // Doesn't necessarily need to be async
    public Task DoSomething() 
    {
        // We're return the wrapped task directly, and adding no additional value.
        return repository.ExecuteAsync();
    }
}

public class Repository : IRepository
{
    public async Task ExecuteAsync()
    {
        using (var connection = new SqlConnection(DbConfiguration.DatabaseConnection))
        {
            // Here we do need to await, otherwise we'll dispose the connection
            return await connection.ExecuteAsync(storedProcedure, parameters, 
              commandType: CommandType.StoredProcedure, 
              commandTimeout: Configuration.TransactionTimeout);
        }
    }
}

// NUnit has full support for async / await
[Test]
public async Task TestMethod()
{
    var repository = new Mock<IRepository>();
    var classToTest =  new ClassToTest(repository.Object);

    repository.Setup(_ => _.ExecuteAsync()).Returns(Task.FromResult((object)null));
    // Moq also has support for async, e.g. .ReturnsAsync

    // You need to await the test.
    await classToTest.DoSomething();

    repository.Verify(p => p.ExecuteAsync(), Times.Once());
}