如何使用moq.dapper模拟QueryMulitiple

时间:2018-09-19 21:41:42

标签: unit-testing moq dapper

我正在编写单元测试用例,并且成功地为Query编写了单元测试用例。但是我无法为QueryMulitiple编写单元测试用例。

对于查询,我这样写:

 IEnumerable<ClientTestPurpose> fakeTestPurposes = new 
 List<ClientTestPurpose>()
 {
      new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name1"},
      new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name2"},
      new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name3"}
 };

 _mock.SetupDapper(x => x.Query<ClientTestPurpose>(It.IsAny<string>(), null, null, true, null, null)).Returns(fakeTestPurposes);

 var result = _libraryRepository.TestPurposes(clientModal.Id);

 Assert.IsNotNull(result);
 Assert.AreEqual(result.Count(), fakeTestPurposes.Count());   

如何为QueryMultiple写作:

using (var multi = _db.QueryMultiple(spName, spParams, commandType: CommandType.StoredProcedure))
{
     var totals = multi.Read<dynamic>().FirstOrDefault();
     var aggregates = multi.Read<StatusModel>();
     var scripts = multi.Read<LibraryItemModel>();
     var runs = multi.Read<RunSummaryModel>();
     var filteredTotals = multi.Read<dynamic>().FirstOrDefault();
}

1 个答案:

答案 0 :(得分:1)

显然,您使用Moq.Dapper扩展。这是SetupDapperSetupDapperAsync方法的代码:

public static ISetup<IDbConnection, TResult> SetupDapper<TResult>(this Mock<IDbConnection> mock, Expression<Func<IDbConnection, TResult>> expression)
{
  MethodCallExpression body = expression.Body as MethodCallExpression;
  if ((body != null ? body.Method.DeclaringType : (Type) null) != typeof (SqlMapper))
    throw new ArgumentException("Not a Dapper method.");
  string name = body.Method.Name;
  if (name == "Execute")
    return (ISetup<IDbConnection, TResult>) DbConnectionInterfaceMockExtensions.SetupExecute(mock);
  if (name == "ExecuteScalar")
    return DbConnectionInterfaceMockExtensions.SetupExecuteScalar<TResult>(mock);
  if (name == "Query" || name == "QueryFirstOrDefault")
    return DbConnectionInterfaceMockExtensions.SetupQuery<TResult>(mock);
  throw new NotSupportedException();
}

public static ISetup<IDbConnection, Task<TResult>> SetupDapperAsync<TResult>(this Mock<IDbConnection> mock, Expression<Func<IDbConnection, Task<TResult>>> expression)
{
  MethodCallExpression body = expression.Body as MethodCallExpression;
  if ((body != null ? body.Method.DeclaringType : (Type) null) != typeof (SqlMapper))
    throw new ArgumentException("Not a Dapper method.");
  if (body.Method.Name == "QueryAsync")
    return DbConnectionInterfaceMockExtensions.SetupQueryAsync<TResult>(mock);
  throw new NotSupportedException();
}

如您所见,Moq.Dapper仅支持ExecuteExecuteScalarQueryQueryAsync方法的模拟。因此,您可能会在尝试模拟NotSupportedException时得到QueryMultiple。要模拟数据库行为,您可能需要先引入另一种抽象级别,如@TrueWill在评论中所述。这只是一个想法,以您的情况为例:

[Test]
public void DoSomethingWithQueryTest()
{
    // Arrange
    IEnumerable<ClientTestPurpose> fakeTestPurposes = new
        List<ClientTestPurpose>
        {
            new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name1" },
            new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name2" },
            new ClientTestPurpose { PurposeID = 1, PurposeName = "Test Purpose name3" }
        };

    var mock = new Mock<ILibraryRepository>();
    mock.Setup(x => x.TestPurposes(It.IsAny<int>())).Returns(fakeTestPurposes);
    var logicService = new SomeLogicService(mock.Object);

    // Act
    var result = logicService.DoSomethingWithQuery(1);

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(result.Count(), fakeTestPurposes.Count());
}

[Test]
public void DoSomethingWithQueryMultipleTest()
{
    // Arrange
    SomeAggregate fakeTestPurposes = new SomeAggregate();

    var mock = new Mock<ILibraryRepository>();
    mock.Setup(x => x.TestQueryMultiple()).Returns(fakeTestPurposes);
    var logicService = new SomeLogicService(mock.Object);

    // Act
    var result = logicService.DoSomethingWithQueryMultiple();

    // Assert
    Assert.IsNotNull(result);
}

public interface ILibraryRepository
{
    IEnumerable<ClientTestPurpose> TestPurposes(int id);
    SomeAggregate TestQueryMultiple();
}

public class LibraryRepository : ILibraryRepository
{
    private readonly IDbConnection _db;

    public LibraryRepository(IDbConnection db)
    {
        _db = db ?? throw new ArgumentNullException(nameof(db));
    }

    public IEnumerable<ClientTestPurpose> TestPurposes(int id)
    {
        return _db.Query<ClientTestPurpose>("SQL here", new { id }, null, true, null, null);
    }

    public SomeAggregate TestQueryMultiple()
    {
        string spName = "SQL here";
        var spParams = new { Id = 1 };
        using (var multi = _db.QueryMultiple(spName, spParams, commandType: CommandType.StoredProcedure))
        {
            return new SomeAggregate
            {
                totals = multi.Read<dynamic>().FirstOrDefault(),
                aggregates = multi.Read<StatusModel>(),
                scripts = multi.Read<LibraryItemModel>(),
                runs = multi.Read<RunSummaryModel>(),
                filteredTotals = multi.Read<dynamic>().FirstOrDefault()
            };
        }
    }
}

public class SomeAggregate
{
    public IEnumerable<dynamic> totals { get; set; }
    public IEnumerable<StatusModel> aggregates { get; set; }
    public IEnumerable<LibraryItemModel> scripts { get; set; }
    public IEnumerable<RunSummaryModel> runs { get; set; }
    public IEnumerable<dynamic> filteredTotals { get; set; }
}

/// <summary>
/// Example logic server, that just returns results from repository
/// </summary>
public class SomeLogicService
{
    private readonly ILibraryRepository _repo;

    public SomeLogicService(ILibraryRepository repo)
    {
        _repo = repo;
    }

    public IEnumerable<ClientTestPurpose> DoSomethingWithQuery(int id)
    {
        return _repo.TestPurposes(id);
    }

    public SomeAggregate DoSomethingWithQueryMultiple()
    {
        return _repo.TestQueryMultiple();
    }
}

主要思想是将所有特定于DB的东西隐藏在ILibraryRepository之后,并将需要测试的所有逻辑移动到某个逻辑服务器,该服务器将存储库作为依赖项。为了使存储库中的代码简单明了,包含所有特定于数据库的逻辑:连接,事务,命令,对象关系映射等。而且,您无需使用unt测试覆盖此代码。但是,您确实使用单元测试覆盖了SomeLogicService的代码,因为这是您真正需要测试的内容。您会看到Dapper扩展方法是相当底层的抽象,它没有隐藏使用DB的细节,它们只是帮助者。希望能帮助到你。