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

Kir*_*rzy -1 c# nunit async-await

我有这个代码

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());
}
Run Code Online (Sandbox Code Playgroud)

测试失败并显示此消息

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

有谁知道为什么?

谢谢

Stu*_*tLC 5

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

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

特别重要的是您要测试的代码中的错误.在范围内Repository.ExecuteAsync调用connection.ExecuteAsync,using但这是不等的.这意味着将在任务完成之前处理连接.您需要将方法更改为asyncawait延迟处理连接的调用.

但是,包装方法DoSomething方法不应该使用Task.Run(),因为它没有为存储库任务添加任何值,也不需要重复async / return await它.

然后调用者(在这种情况下你的单元测试)等待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());
}
Run Code Online (Sandbox Code Playgroud)

  • 我不得不玩我自己的扩展方法来实现它.:) (2认同)