使用NUnit / NSubstitute / AutoFixture和InsightDatabase模拟DbConnection

时间:2016-08-17 08:42:51

标签: c# nunit autofixture nsubstitute

我们正在使用Nunit,NSubstitute和AutoFixture来测试构建在Insight数据库之上的存储库类...

[TestFixture]
public class CalculationResultsRepositoryTests
{
    private IFixture _fixture;

    private IDbConnection _connection;
    private CalculationResultsRepository _calculationResultsRepository;

    [SetUp]
    public void Setup()
    {
        _fixture = new Fixture().Customize(new AutoConfiguredNSubstituteCustomization());
        _connection = _fixture.Freeze<IDbConnection>();
        _calculationResultsRepository = _fixture.Create<CalculationResultsRepository>();
    }

    [Test]
    public void TestReturnsPagedCalculationResults()
    {
        //Arrange
        var financialYear = _fixture.Create<int>();
        var pagedResults = _fixture.Create<PagedResults<ColleagueCalculationResult>>();
        _connection.QueryAsync(Arg.Any<string>(), Arg.Any<object>(), Arg.Any<IQueryReader<PagedResults<ColleagueCalculationResult>>>()).Returns(pagedResults);

        //Act
        var result = _calculationResultsRepository.PagedListAsync(financialYear);

        //Assert
        Assert.IsInstanceOf<PagedResults<ColleagueCalculationResult>>(result);
    }
}

但是,在运行测试时,我们会看到以下异常:

  

System.Reflection.TargetInvocationException:调用目标抛出了异常。     ----&GT; NSubstitute.Exceptions.UnexpectedArgumentMatcherException:参数匹配器(Arg.Is,Arg.Any)只应用于代替成员参数。不要在Returns()语句或成员调用之外的任何其他地方使用。   正确使用:     sub.MyMethod(Arg.Any())。返回( “HI”)   使用不正确:     sub.MyMethod( “HI”),返回(Arg.Any())

我们对如何解决这个问题感到有点失落,但是在猜测它似乎与返回类型有关,它被定义为QueryAsync()的特定重载参数中的泛型InsightDatabase中的扩展方法:

public static Task<T> QueryAsync<T>(this IDbConnection connection, string sql, object parameters, IQueryReader<T> returns, CommandType commandType = CommandType.StoredProcedure, CommandBehavior commandBehavior = CommandBehavior.Default, int? commandTimeout = default(int?), IDbTransaction transaction = null, CancellationToken? cancellationToken = default(CancellationToken?), object outputParameters = null);

有人知道如何成功嘲笑这个吗?

为了完整性,我们试图替换的方法调用是:

var results = await _connection.QueryAsync("GetCalculationResults", new { FinancialYearId = financialYearId, PageNumber = pageNumber, PageSize = pageSize },
                Query.ReturnsSingle<PagedResults<ColleagueCalculationResult>>()
                    .ThenChildren(Some<ColleagueCalculationResult>.Records));

2 个答案:

答案 0 :(得分:1)

我根据您的测试做了一些更改。看看它是否有帮助。

[Test]
public async Task TestReturnsPagedCalculationResults()
{
    //Arrange
    var financialYear = _fixture.Create<int>();
    var pagedResults = _fixture.Create<PagedResults<ColleagueCalculationResult>>();
    _connection.QueryAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(pagedResults));

    //Act
    var result = await _calculationResultsRepository.PagedListAsync(financialYear);

    //Assert
    Assert.IsInstanceOf<PagedResults<ColleagueCalculationResult>>(result);
}

答案 1 :(得分:1)

这可能不是最好的方法,但是因为你不能模拟扩展方法而我没有时间编写Insight的测试实现,这似乎是现在可以接受的解决方案...

创建了IInsightDatabase接口:

public interface IInsightDatabase
{
    Task<T> QueryAsync<T>(string sql, object parameters, IQueryReader<T> returns, CommandType commandType = CommandType.StoredProcedure, CommandBehavior commandBehavior = CommandBehavior.Default, int? commandTimeout = default(int?), IDbTransaction transaction = null, CancellationToken? cancellationToken = default(CancellationToken?), object outputParameters = null);
}

创建了IInsightDatabase的具体实现:

public class InsightDatabase : IInsightDatabase
{
    private readonly IDbConnection _connection;

    public InsightDatabase(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<T> QueryAsync<T>(string sql, object parameters, IQueryReader<T> returns, CommandType commandType = CommandType.StoredProcedure, CommandBehavior commandBehavior = CommandBehavior.Default, int? commandTimeout = default(int?), IDbTransaction transaction = null, CancellationToken? cancellationToken = default(CancellationToken?), object outputParameters = null)
    {
        return await _connection.QueryAsync(sql, parameters, returns, commandType, commandBehavior, commandTimeout, transaction, cancellationToken, outputParameters);
    }
}

现在将具体实现注入到存储库类中,允许通过模拟IInsightDatabase进行测试:

private IFixture _fixture;

private IInsightDatabase _insightDatabase;
private CalculationResultsRepository _calculationResultsRepository;

[SetUp]
public void Setup()
{
    _fixture = new Fixture().Customize(new AutoConfiguredNSubstituteCustomization());
    _insightDatabase = _fixture.Freeze<IInsightDatabase>();
    _calculationResultsRepository = _fixture.Create<CalculationResultsRepository>();
}

[Test]
public async Task PagedListAsync_ReturnsPagedResults()
{
    //Arrange
    var financialYearId = _fixture.Create<int>();
    var pagedResults = _fixture.Create<PagedResults<ColleagueCalculationResult>>();
    _insightDatabase.QueryAsync(Arg.Any<string>(), Arg.Any<object>(), Arg.Any<IQueryReader<PagedResults<ColleagueCalculationResult>>>()).Returns(pagedResults);

    //Act
    var result = await _calculationResultsRepository.PagedListAsync(financialYearId);

    //Assert
    result.Should().NotBeNull();
    result.Should().BeOfType<PagedResults<ColleagueCalculationResult>>();
    result.Should().Be(pagedResults);
}

TAH-DAH!存储库类现在是可测试的,Insights与IDbConnection的交易,对扩展方法的调用以及所有其他肮脏的东西很好地隐藏在一些虽然不可测试但应该相当难以破解的东西中。