如何使用Moq.Dapper模拟QueryAsync<dynamic>?
问题描述
我有一个在事务中查询并删除指定表记录的方法:
public async Task<IEnumerable<DataTableRecordsResponse>> DeleteDataAsync(string tenantId) { if (string.IsNullOrEmpty(tenantId)) { throw new ArgumentNullException(nameof(tenantId)); } var deletedRecords = new List<DataTableRecordsResponse>(); try { using (IDbConnection conn = _sqlDbConnection.CreateConnection()) { conn.Open(); using (IDbTransaction transaction = conn.BeginTransaction()) { try { foreach (string table in s_allowedTableName) { string selectQuery = $"SELECT * FROM {table} WHERE TenantId = @tenantId;"; IEnumerable<dynamic> records = await _dapperWrapper.QueryAsync<dynamic>(conn, selectQuery, new { tenantId }, transaction); deletedRecords.Add(new DataTableRecordsResponse(table, records)); if (records.Any()) { string deleteCommand = $"DELETE FROM {table} WHERE TenantId = @tenantId; "; await _dapperWrapper.ExecuteAsync(conn, deleteCommand, new { tenantId }, transaction); } } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); _logger.LogException(ex); throw; } finally { _logger.LogEvent("Tenant data purge operation completed"); } } } } catch (Exception ex) { _logger.LogException(ex); throw; } return deletedRecords; }
尝试用以下测试代码模拟QueryAsync<dynamic>时,返回值始终为null:
[TestMethod] public async Task Successful_Transaction_Should_Complete_Transaction_And_Return_Deleted_Records() { //Arrange _sqlDbConnection.Setup(c => c.CreateConnection()).Returns(_dbMock.Object); _bulkOperationHelper = new BulkOperationHelper(_sqlDbConnection.Object, _loggerMock.Object); _dbMock.SetupDapperAsync(c => c.QueryAsync<dynamic>(It.IsAny<string>(), It.IsAny<object>(), null, null, null)) .ReturnsAsync(_results); _dbMock.SetupDapper(c => c.ExecuteScalar(It.IsAny<string>(), It.IsAny<object>(), null, null, null)) .Returns(0); //Act List<DataTableRecordsResponse> deletedRecords = await _bulkOperationHelper.TenantDecommissioningBatchDelete(_tenantId, _tables); //Assert Assert.IsNotNull(deletedRecords); Assert.IsTrue(deletedRecords.Count > 0); Assert.IsTrue(deletedRecords.Count == 0); }
已知Moq.Dapper存在相关未解决问题,现需解决该模拟返回null的问题。
解决方案
方案1:用ExpandoObject构造dynamic数据并修正Mock匹配规则
Moq.Dapper对dynamic泛型方法的支持存在缺陷,同时原测试代码存在参数匹配错误:
- 原Mock中
QueryAsync的事务参数写为null,但实际方法传入了事务对象,导致Mock不匹配返回null - 原方法调用的是
ExecuteAsync,但测试Mock的是ExecuteScalar,同样不匹配
调整后的测试代码:
// 构造dynamic测试数据 var mockRecords = new List<dynamic>(); var record = new ExpandoObject(); record.TryAdd("Id", 1); record.TryAdd("TenantId", _tenantId); record.TryAdd("Column1", "test-data"); mockRecords.Add(record); // 修正QueryAsync的Mock,匹配事务参数 _dbMock.Setup(c => c.QueryAsync<dynamic>( It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>(), It.IsAny<int?>(), It.IsAny<CommandType?>())) .ReturnsAsync(mockRecords as IEnumerable<dynamic>); // 修正ExecuteAsync的Mock,匹配原方法调用 _dbMock.SetupDapperAsync(c => c.ExecuteAsync( It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>(), It.IsAny<int?>(), It.IsAny<CommandType?>())) .ReturnsAsync(1);
方案2:替换dynamic为具体DTO类
如果业务允许,将dynamic替换为具体的DTO类,让Moq.Dapper的泛型支持正常工作:
// 定义通用记录DTO public class TenantRecord { public int Id { get; set; } public string TenantId { get; set; } // 根据表结构添加对应字段 } // 修改原方法中的QueryAsync调用 IEnumerable<TenantRecord> records = await _dapperWrapper.QueryAsync<TenantRecord>(conn, selectQuery, new { tenantId }, transaction); // 测试中正常Mock _dbMock.SetupDapperAsync(c => c.QueryAsync<TenantRecord>( It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>(), It.IsAny<int?>(), It.IsAny<CommandType?>())) .ReturnsAsync(new List<TenantRecord> { new TenantRecord { Id = 1, TenantId = _tenantId } });
方案3:直接Mock DapperWrapper接口
如果代码中使用了_dapperWrapper封装层,直接Mock这个接口可以完全绕过Moq.Dapper的问题:
// 假设IDapperWrapper定义 public interface IDapperWrapper { Task<IEnumerable<T>> QueryAsync<T>(IDbConnection conn, string sql, object param, IDbTransaction transaction); Task<int> ExecuteAsync(IDbConnection conn, string sql, object param, IDbTransaction transaction); } // 测试中Mock该接口 _dapperWrapperMock.Setup(w => w.QueryAsync<dynamic>( It.IsAny<IDbConnection>(), It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>())) .ReturnsAsync(mockRecords); _dapperWrapperMock.Setup(w => w.ExecuteAsync( It.IsAny<IDbConnection>(), It.IsAny<string>(), It.IsAny<object>(), It.IsAny<IDbTransaction>())) .ReturnsAsync(1);
内容的提问来源于stack exchange,提问作者Mahyar Mottaghi Zadeh
相关产品推荐
相关产品推荐

