user5602591
user5602591

Reputation:

How to Mock QueryMultiple using Moq.Dapper

I am writing unit test cases and I am successful in writing unit test case for Query. But I am failing to write unit test case for QueryMultiple.

For Query I am writing like this:

 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());   

How to write for 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();
}

Upvotes: 3

Views: 12275

Answers (1)

KozhevnikovDmitry
KozhevnikovDmitry

Reputation: 1720

Apparently you use Moq.Dapper extenstions. Here is the code of SetupDapper and SetupDapperAsync method:

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();
}

As you can see Moq.Dapper supports mocking only for Execute, ExecuteScalar, Query and QueryAsync methods. Therefore you probably get NotSupportedException on trying to mock QueryMultiple. To mock DB behavior you probably need introduce another level of abstraction first, as @TrueWill said in a comments. Here is just an example of idea how it can be in your case:

[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();
    }
}

The main idea is to hide all DB specific thing behind the ILibraryRepository and move all logic that you need to test to some logic server, that will receive repository as dependency. In order code in repository should be simple, obvious, contains all DB specific logic: connection, transaction, command, object-relation mapping, etc. And you don't need to cover this code with unt tests. However you do cover code of SomeLogicService with unit tests, because this is what you really need to test. You see Dapper extension method are rather low-level abstraction, that doesn't hide details of working with DB, they are just helpers. Hope it helps.

Upvotes: 6

Related Questions