如何模拟(或不模拟)IDbConnection 进行测试?

问题描述 投票:0回答:3

我在测试 IDBconnection 模拟时遇到了麻烦,因为它给了我一个空数据库。 请注意,对于我的 IConfiguration,而不是模拟,我创建了一个 appsettingstest.json,其中包含与数据库的连接字符串以用于测试目的。 但对于 IDbConnection,我不知道如何传递 ConnectionString,因为这是我使用 Dapper 的第一个项目。

这是我的处理程序构造函数:

IDbConnection _dbConnection;
Context _context;
SigSettings _settings;
public SignalsHandler(IConfiguration configuration, IDbConnection connection)
{
     _dbConnection = connection;
     _settings = configuration.GetSection("SigSettings").Get<SigSettings>();
     if (_settings == null)
     _settings = new SigSettings();
}

这是我的测试类构造函数:

private Mock<IDbConnection> _connection = new Mock<IDbConnection>();
private readonly SignalsHandler _handler;
private readonly IConfiguration _configuration;
private readonly SigSettings _settings;
public SignalsTest()
{
      _configuration = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile(@"appsettingstest.json", false, false)
          .AddEnvironmentVariables()
          .Build();
            _connection.Setup(x => x.ConnectionString).Returns(path);

      _settings = _configuration.GetSection("SigSettings").Get<SigSettings>();
      if (_settings == null)
          _settings = new SigSettings();
      _handler = new SignalsHandler(_configuration, _connection.Object);
}

这有效,但是当我执行测试方法时,例如这个虚拟测试:

public  void CanGetAllSignals()
{
      _connection.Setup(x => x.ConnectionString);
      var result =  _handler.GetSignals();
      Assert.IsNotNull(result);
}

它调用这个方法:

public async Task<List<SignalsDTO>> GetSignals()
{
    var res = new List<SignalsDTO>();
    var sigList = await _dbConnection.QueryAsync<SigSignal>(_settings.Signals);
    foreach (var s in sigList)
    res.Add(new SignalsDTO(){IDTag = s.IDTag, Name = s.Name});
    return res;
}

这就是我遇到问题的地方,因为它在这一行给了我一个

NullReferenceException: 'Object reference not set to an instance of an object'
var sigList = await _dbConnection.QueryAsync<SigSignal>(_settings.Signals);
,如果你查看
_dbConnection
,你可以看到它是一个模拟对象,并且数据库为空,所以这就是失败。

我该如何解决这个问题?我试图嘲笑它,但也许这不是最好的方法。

c# unit-testing mstest dapper
3个回答
5
投票

问题在于该方法是扩展方法,而不是对象本身的方法。根据您使用的模拟框架,可能还有一种方法可以模拟扩展方法(更新:kuldeep 的答案显示了一种潜在的方法)。

但是,由于您专门使用 Dapper,因此您可以查看现成的解决方案,例如 https://github.com/UnoSD/Moq.Dapper for Moq。

它允许您编写如下测试:

[Test]
public async Task QueryAsyncGeneric()
{
    var connection = new Mock<DbConnection>();

    var expected = new[] { 7, 77, 777 };

    connection.SetupDapperAsync(c => c.QueryAsync<int>(It.IsAny<string>(), null, null, null, null))
              .ReturnsAsync(expected);

    var actual = (await connection.Object.QueryAsync<int>("", null, null, null, null)).ToList();

    Assert.That(actual.Count, Is.EqualTo(expected.Length));
    Assert.That(actual, Is.EquivalentTo(expected));
}

0
投票

您必须像下面这样设置 QueryAsync 的期望

this._connection
            .Setup(s => s.QueryAsync<SigSignal>(
                It.IsAny<Signals>())
            .ReturnsAsync(yourFakeSigListWithTestData);

在 It.IsAny() 中,你应该传入正确的对象,我只是在这里放置信号,但理想情况下它必须是 _settings.Signals 类型的东西

编辑:

另一种可能的方法是你自己制作一个存根。实现 IDbConnection,然后将该存根作为依赖项传递,您可以在这个存根中基本上提供您自己的 IDbConnection 方法的实现,这些方法实际上在您的测试路径中被调用。

为了存根 IDbConnection 看一下这个 answer


0
投票

我建议将此 Moq 扩展用于

Dapper
方法:https://github.com/UnoSD/Moq.Dapper。它提供了对许多方法的方便模拟,例如

  • QueryFirstOrDefault()
  • QueryAsync()
  • Query()
  • ExecuteScalar()
  • ExecuteScalarAsync()
  • ...

这是随附的示例

DapperTest.cs

[Test]
public void QueryAsyncGeneric()
{
    var connection = new Mock<DbConnection>();

    var expected = new[] { 7, 77, 777 };

    connection
        .SetupDapperAsync(c => c.QueryAsync<int>(It.IsAny<string>(), null, null, null, null))
        .ReturnsAsync(expected);

    var actual = connection.Object
        .QueryAsync<int>("")
        .GetAwaiter()
        .GetResult()
        .ToList();
        
    Assert.That(actual.Count, Is.EqualTo(expected.Length));
    Assert.That(actual, Is.EquivalentTo(expected));
}
© www.soinside.com 2019 - 2024. All rights reserved.