Reputation: 65534
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:
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
:
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
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