C# 使用NUnit/NSubstitute/AutoFixture和InsightDatabase模拟数据库连接

C# 使用NUnit/NSubstitute/AutoFixture和InsightDatabase模拟数据库连接,c#,nunit,autofixture,nsubstitute,C#,Nunit,Autofixture,Nsubstitute,我们正在使用Nunit、NSubstitute和AutoFixture来测试构建在Insight数据库之上的存储库类 [TestFixture] public class CalculationResultsRepositoryTests { private IFixture _fixture; private IDbConnection _connection; private CalculationResultsRepository _calculationResu

我们正在使用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);
    }
}
[TestFixture]
公共类CalculationResultsRepositoryTests
{
私人IFixture_固定装置;
专用IDBU连接;
私有CalculationResultsRepository\u CalculationResultsRepository;
[设置]
公共作废设置()
{
_fixture=新fixture().Customize(新的自动配置nsubstitutecustomization());
_连接=_fixture.Freeze();
_calculationResultsRepository=_fixture.Create();
}
[测试]
public void TestReturnsPagedCalculationResults()
{
//安排
var financialYear=_fixture.Create();
var pagedResults=_fixture.Create();
_QueryAsync(Arg.Any(),Arg.Any(),Arg.Any())。返回(pagedResults);
//表演
var结果=_calculationResultsRepository.PagedListSync(财务年度);
//断言
Assert.IsInstanceOf(结果);
}
}
但是,在运行测试时,我们会看到以下异常:

System.Reflection.TargetInvocationException:调用的目标已引发异常。 ---->NSSubstitute.Exceptions.UnexpectedArgumentMatcherException:参数匹配器(Arg.Is,Arg.Any)只能用于代替成员参数。不要在Returns()语句中使用,也不要在成员调用之外的任何地方使用。 正确使用: sub.MyMethod(Arg.Any())。返回(“hi”) 错误使用: sub.MyMethod(“hi”).返回(Arg.Any())

我们对如何解决这个问题有点不知所措,但据猜测,这似乎与InsightDatabase中QueryAsync()扩展方法的这个特定重载的参数中定义为泛型的返回类型有关:

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);
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);
    }
}
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);
}
公共静态任务QuerySync(此IDbConnection连接,字符串sql,对象参数,IQueryReader返回,CommandType CommandType=CommandType.StoredProcedure,CommandBehavior CommandBehavior=CommandBehavior.Default,int?commandTimeout=Default(int?),IDBTransation 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));
var results=wait\u connection.QueryAsync(“GetCalculationResults”,新的{FinancialYearId=FinancialYearId,PageNumber=PageNumber,PageSize=PageSize},
Query.ReturnsSingle()
.然后是儿童(一些记录);

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

[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);
}
[测试]
公共异步任务TestReturnsPagedCalculationResults()
{
//安排
var financialYear=_fixture.Create();
var pagedResults=_fixture.Create();
_QueryAsync(null,null,null).ReturnsForAnyArgs(Task.FromResult(pagedResults));
//表演
var结果=等待_calculationResultsRepository.PagedListSync(财务年度);
//断言
Assert.IsInstanceOf(结果);
}

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

[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);
}
[测试]
公共异步任务TestReturnsPagedCalculationResults()
{
//安排
var financialYear=_fixture.Create();
var pagedResults=_fixture.Create();
_QueryAsync(null,null,null).ReturnsForAnyArgs(Task.FromResult(pagedResults));
//表演
var结果=等待_calculationResultsRepository.PagedListSync(财务年度);
//断言
Assert.IsInstanceOf(结果);
}

这可能不是最好的方法,但由于您无法模拟扩展方法,而且我没有时间编写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
{
任务QuerySync(字符串sql,对象参数,IQueryReader返回,CommandType CommandType=CommandType.StoredProcess,CommandBehavior CommandBehavior=CommandBehavior.Default,int?commandTimeout=Default(int?),IDBTransation transaction=null,CancellationToken?CancellationToken=Default(CancellationToken?),对象outputParameters=null);
}
创建IInsightDatabase的具体实现:

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);
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);
    }
}
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);
}
公共类InsightDatabase:IInsightDatabase
{
专用只读IDbConnection\u连接;
公共InsightDatabase(IDbConnection)
{
_连接=连接;
}
公共异步任务QueryAsync(字符串sql,对象参数,IQueryReader返回,CommandType CommandType=CommandType.StoredProcess,CommandBehavior=CommandBehavior.Default,int?commandTimeout=Default(int?),IDBTransation transaction=null,CancellationToken?CancellationToken=Default(CancellationToken?),对象outputParameters=null)
{
return wait_connection.QueryAsync(sql、参数、返回、commandType、commandBehavior、commandTimeout、transaction、cancellationToken、outputParameters);
}
}
具体的实现现在被注入到repository类中,允许通过模拟IInsightDatabase对其进行测试:

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);
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);
    }
}
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);
}
专用IFixture\u夹具;
私有IIInsightDatabase_insightDatabase;
私有CalculationResultsRepository\u CalculationResultsRepository;
[设置]
公共作废设置()
{
_fixture=新fixture().Customize(新的自动配置nsubstitutecustomization());
_insightDatabase=_fixture.Freeze();
_calculationResultsRepository=_fixture.Create();
}
[测试]
公共异步任务页面同步_ReturnsPagedResults()
{
//安排
var financialYearId=_fixture.Create();
var pagedResults=_fixture.Create();
_insightDatabase.QueryAsync(Arg.Any(),Arg.Any(),Arg.Any())。返回(pagedResults);
//表演
var result=await_calculationResultsRepository.pagedListSync(financialYearId);
//断言
结果,寿