使用NSubstitute模拟Dapper的IDbConnection时遇类型不匹配异常
问题原因分析
Dapper的QueryFirstOrDefaultAsync是扩展方法,并非IDbConnection接口的原生成员。NSubstitute仅能模拟接口/类的原生成员,无法直接配置扩展方法的返回值。你当前的写法会让NSubstitute误将扩展方法的调用解析为对IDbConnection内部成员(如CommandText的setter)的调用,从而抛出类型不匹配异常。
解决方案
推荐两种可行方案,优先选择第一种(封装接口),更简洁易维护:
方案1:封装Dapper操作到自定义接口
1. 定义Dapper封装接口与实现
// 定义封装接口 public interface IDapperWrapper { Task<T> QueryFirstOrDefaultAsync<T>( IDbConnection connection, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null); } // 实现封装类 public class DapperWrapper : IDapperWrapper { public async Task<T> QueryFirstOrDefaultAsync<T>( IDbConnection connection, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { return await connection.QueryFirstOrDefaultAsync<T>(sql, param, transaction, commandTimeout, commandType); } }
2. 修改控制器代码,注入封装接口
private readonly IDapperContextBuilder _dapperContextBuilder; private readonly IDapperWrapper _dapperWrapper; public YourController(IDapperContextBuilder dapperContextBuilder, IDapperWrapper dapperWrapper) { _dapperContextBuilder = dapperContextBuilder; _dapperWrapper = dapperWrapper; } public async Task UpdateStatus(AuthorityStatusUpdateInputDto input) { using var connection = _dapperContextBuilder.CreateConnection(); Authority authority = await _dapperWrapper.QueryFirstOrDefaultAsync<Authority>(connection, "SELECT TOP 1 * FROM Authority"); // 其余代码 }
3. 单元测试中模拟封装接口
[Fact] public async Task GetAllAuthority() { // Arrange var connectionFactory = Substitute.For<IDapperContextBuilder>(); var fakeConnection = Substitute.For<IDbConnection>(); connectionFactory.CreateConnection().Returns(fakeConnection); var dapperWrapper = Substitute.For<IDapperWrapper>(); var authority = new MasterData.Authority(); dapperWrapper.QueryFirstOrDefaultAsync<MasterData.Authority>(Arg.Any<IDbConnection>(), Arg.Any<string>()) .ReturnsForAnyArgs(Task.FromResult(authority)); // 实例化控制器 var controller = new YourController(connectionFactory, dapperWrapper); // Act await controller.UpdateStatus(new AuthorityStatusUpdateInputDto()); // Assert // 验证调用逻辑,例如是否正确传入SQL语句 await dapperWrapper.Received().QueryFirstOrDefaultAsync<MasterData.Authority>( Arg.Any<IDbConnection>(), "SELECT TOP 1 * FROM Authority" ); }
方案2:模拟Dapper底层依赖(繁琐,不推荐)
通过模拟IDbCommand、IDataReader等底层对象,让Dapper从模拟的读取器中获取数据:
[Fact] public async Task GetAllAuthority() { // Arrange var connectionFactory = Substitute.For<IDapperContextBuilder>(); var fakeConnection = Substitute.For<IDbConnection>(); var fakeCommand = Substitute.For<IDbCommand>(); var fakeReader = Substitute.For<IDataReader>(); // 模拟Connection创建Command fakeConnection.CreateCommand().Returns(fakeCommand); // 模拟Command异步执行返回Reader fakeCommand.ExecuteReaderAsync(Arg.Any<CancellationToken>()).Returns(Task.FromResult(fakeReader)); // 模拟Reader返回数据(需根据Authority的属性配置对应读取逻辑) fakeReader.Read().Returns(true, false); // 先返回true表示有数据,再false结束读取 fakeReader.GetOrdinal("Id").Returns(0); fakeReader.GetInt32(0).Returns(1); fakeReader.GetOrdinal("Name").Returns(1); fakeReader.GetString(1).Returns("Admin"); connectionFactory.CreateConnection().Returns(fakeConnection); // 实例化控制器 var controller = new YourController(connectionFactory); // Act await controller.UpdateStatus(new AuthorityStatusUpdateInputDto()); // Assert // 验证CommandText是否正确设置 fakeCommand.Received().CommandText = "SELECT TOP 1 * FROM Authority"; }
内容的提问来源于stack exchange,提问作者San Jaisy
相关产品推荐
相关产品推荐

