Jeremy Thompson
Jeremy Thompson

Reputation: 65534

Moq Dapper - Multi-map error: splitOn column 'Id' was not found

I'm running into this problem, let me describe it with my code: https://github.com/UnoSD/Moq.Dapper/issues/20


I'm trying to mock a QueryAsync call using Moq.Dapper library and hitting the error:

Multi-map error: splitOn column 'Id' was not found

Here is the multi mapping query and code that works perfect:

var query = $@" SELECT * FROM [{nameof(Dashboard)}] dashboard 
                left join [{nameof(DashboardUser)}] dusers on dashboard.DashboardId = dusers.DashboardId
                left join [Users] users on dusers.UserId = users.Id
                WHERE dashboard.{nameof(accountId)} = @{nameof(accountId)} AND dashboard.{nameof(dashboardId)} = @{nameof(dashboardId)}";

Dashboard? dashboardEntity = null;
using (var connection = _context.CreateConnection())
{
    var result = await connection.QueryAsync<Dashboard, DashboardUser, User, Dashboard?>(query,
        (dashboard, dashboardUser, user) =>
        {
            if (dashboard == null) return null;
            if (dashboardEntity == null)
            {
                dashboardEntity = dashboard;
                dashboardEntity.Users = new List<DashboardUser>();
            }
            if (dashboardUser != null)
            {
                dashboardEntity.Users.Add(dashboardUser);
                if (user != null) dashboardUser.User = user;
            }

            return dashboardEntity;
        }, splitOn: $@"{nameof(Dashboard.DashboardId)},{nameof(Dashboard.DashboardId)},Id", param: new { accountId, dashboardId });
}

When Mocking the QueryAsync call I'm hitting the same problem as everyone else in the GitHub thread:

Func<Dashboard, DashboardUser, User, Dashboard?> funMap = (Dashboard, DashboardUser, User) => Mock.Of<Dashboard>();


public async Task Should_Get_Dashboard()
{
// Arrange
var connection = new Mock<DbConnection>();
IEnumerable<Dashboard> expected = GenFu.ListOf<Dashboard>(1);

connection.SetupDapperAsync(c => c.QueryAsync(It.IsAny<string>(), funMap, It.IsAny<object>(), It.IsAny<IDbTransaction>(),It.IsAny<bool>(), 
 "DashboardId,DashboardId,Id", // <-- SplitOn defined but error is *Multi-map error: splitOn column 'Id' was not found*
 It.IsAny<int?>(), It.IsAny<CommandType?>()))
.ReturnsAsync(expected);

_context.Setup(x => x.CreateConnection()).Returns(connection.Object);
var dashboardRepository = new DashboardRepository(_context.Object, null, null);
CommonResult<Dashboard?> actual = new();

// Act
actual = await dashboardRepository.GetDashboard(3, 1);
}

Does anyone have a solution for this? It's the final thing for my Unit Test code coverage.

UPDATE:

I don't need to specify the mapping. See how the code TFirst, TSecond, TThird, TResult is faint/dimmed and I get the same problem even with it explicitly mapped:

enter image description here

I did try this method:

connection.SetupDapperAsync(c => c.QueryAsync<Dashboard>(It.IsAny<string>(), It.IsAny<Type[]>(), GetFunc, It.IsAny<object>(), It.IsAny<IDbTransaction>(), It.IsAny<bool>(), $@"{nameof(Dashboard.DashboardId)},{nameof(Dashboard.DashboardId)},Id", It.IsAny<int?>(), It.IsAny<CommandType?>()))
        .ReturnsAsync(expected);

and got the error:

System.ArgumentException : When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id (Parameter 'splitOn')

I checked the Dapper code and tried * for the SplitOn and that made no difference.

UPDATE 2:

The problem is the MultiMapAsync method and I can't mock it because its scope is private:

enter image description here

UPDATE 3:

I changed the Dapper method to Public to see if I could Mock it:

Func<Dashboard, DashboardUser, User, Dashboard?> funMap = (Dashboard, DashboardUser, User) => Mock.Of<Dashboard>();
private class DontMap { /* hiding constructor */ }

public async Task Should_Get_Dashboard()
{    
    // Arrange
    var connection = new Mock<DbConnection>();
    IEnumerable<Dashboard> expected = GenFu.ListOf<Dashboard>(1);

    connection.SetupDapperAsync(c => c.MultiMapAsync<Dashboard, DashboardUser, User, DontMap, DontMap, DontMap, DontMap, Dashboard?>(new CommandDefinition(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>(), It.IsAny<int>(),It.IsAny<CommandType>(), CommandFlags.None, default), funMap, $@"{nameof(Dashboard.DashboardId)},{nameof(Dashboard.DashboardId)},Id")).ReturnsAsync(expected);

I get the error:

Specified method is not supported

Upvotes: 0

Views: 2167

Answers (1)

Palle Due
Palle Due

Reputation: 6292

Mocking Dapper extension methods provides no value. You end up just testing your own test code. I would do either an integration test, where you test the whole method on a small database, or pull the testable logic out of the Dapper call like this:

using (var connection = _context.CreateConnection())
{
    var result = await connection.QueryAsync<Dashboard, DashboardUser, User, Dashboard?>(query,
        (dashboard, dashboardUser, user) => CreateDashboardEntity(dashboardEntity, dashboard, dashboardUser, user), 
        splitOn: $@"{nameof(Dashboard.DashboardId)},{nameof(Dashboard.DashboardId)},Id", param: new { accountId, dashboardId });
}

public Dashboard? CreateDashboardEntity(Dashboard? dashboardEntity, Dashboard dashboard, DashboardUser dashboardUser, User user)
{
    if (dashboard == null) return null;
    if (dashboardEntity == null)
    {
        dashboardEntity = dashboard;
        dashboardEntity.Users = new List<DashboardUser>();
    }
    if (dashboardUser != null)
    {
        dashboardEntity.Users.Add(dashboardUser);
        if (user != null) dashboardUser.User = user;
    }
    return dashboardEntity;
}

Now you can unit-test CreateDashBoardEntity as much as you like, and there is no logic left in QueryAsync that haven't already been tested by the good Dapper developers.

Upvotes: 1

Related Questions