【问题标题】:How to mock (or not) IDbConnection for testing?如何模拟(或不模拟)IDbConnection 进行测试?
【发布时间】:2021-10-27 19:21:06
【问题描述】:

我在测试 IDBconnection Mock 时遇到问题,因为它给了我一个空数据库。 请注意,对于我的 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&lt;SigSignal&gt;(_settings.Signals);,如果你查看_dbConnection,你可以看到它是一个模拟对象并且数据库为空,所以这就是失败。

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

【问题讨论】:

    标签: c# unit-testing mstest dapper


    【解决方案1】:

    问题在于该方法是扩展方法,而不是对象本身的方法。根据您使用的模拟框架,也可能有一种模拟扩展方法的方法(更新: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));
    }
    

    【讨论】:

    • 我无法使用该代码,但它非常酷。可以在没有模拟的情况下以粗略的方式建立我的连接吗?我知道没有最好的方法,但我的老师让我这样做以保持简单(或者可能是因为他不知道该怎么做:/)
    • 连接是特定于数据库的,所以除非你可以有某种 InMemoryConnection,否则我认为你不能在没有嘲笑的情况下做到这一点。即使是 InMemoryConnection 也可能无法与 Dapper 一起使用。为什么上面的代码对你不起作用?
    【解决方案2】:

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

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

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

    编辑:

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

    为了存根 IDbConnection 看看这个answer

    【讨论】:

    • 它对我不起作用。可能是一种只设置连接字符串而不以粗略方式模拟的方法吗?我知道没有最好的方法,但我的老师让我这样做以保持简单(或者可能是因为他不知道该怎么做:/)
    • 另一种可能的方式是你自己做一个存根。实现 IDbConnection 然后将该存根作为依赖项传递,您可以在此存根中基本上提供您自己的方法实现,这些方法实际上在您的测试路径中被调用
    猜你喜欢
    • 2021-08-26
    • 2014-01-01
    • 2013-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-28
    • 2018-04-04
    相关资源
    最近更新 更多