如何使用Moq模拟StackExchange.Redis ConnectionMultiplexer类?

时间:2015-02-04 16:02:07

标签: c# .net unit-testing moq stackexchange.redis

我正在努力模拟与StackExchange.Redis库相关的行为,但无法弄清楚如何正确模拟它使用的密封类。一个具体的例子是在我的调用代码中我做了这样的事情:

    var cachable = command as IRedisCacheable;

    if (_cache.Multiplexer.IsConnected == false)
    {
        _logger.Debug("Not using the cache because the connection is not available");

        cacheAvailable = false;
    }
    else if (cachable == null)
    {

其中的关键行是 _cache.Multiplexer.IsConnected ,我在检查之前确保在使用缓存之前有一个有效的连接。所以在我的测试中,我想用这样的东西来模拟这种行为:

    _mockCache = new Mock<IDatabase>();
    _mockCache.Setup(cache => cache.Multiplexer.IsConnected).Returns(false);

但是,虽然该代码编译得很好,但在运行测试时出现此错误:

Moq exception thrown within the test

我也试过模拟多路复用器类本身,并将其提供给我的模拟缓存,但我遇到了多路复用器类被密封的事实:

    _mockCache = new Mock<IDatabase>();
    var mockMultiplexer = new Mock<ConnectionMultiplexer>();
    mockMultiplexer.Setup(c => c.IsConnected).Returns(false);
    _mockCache.Setup(cache => cache.Multiplexer).Returns(mockMultiplexer.Object);

...但这会导致此错误:

The error thrown when trying to mock a sealed class

最终我想在我的测试中控制该属性是真还是假,所以有没有正确的方法来模拟这样的东西?

3 个答案:

答案 0 :(得分:10)

我认为最好的方法是将所有Redis交互包装在您自己的类和接口中。像CacheHandler : ICacheHandlerICacheHandler之类的东西。您的所有代码都只会与ICacheHandler对话。

这样,您就消除了对Redis的强烈依赖(您可以根据需要更换ICacheHandler的实现)。您还可以模拟与缓存层的所有交互,因为它是针对接口编程的。

您不应该直接测试StackExchange.Redis - 这不是您编写的代码。

答案 1 :(得分:5)

在您自己的班级中使用界面IConnectionMultiplexer而不是具体的班级ConnectionMultiplexer

public interface ICacheable
{
   void DoYourJob();
}

public sealed class RedisCacheHandler : ICacheable
{
    private readonly IConnectionMultiplexer multiplexer;

    public RedisCacheHandler(IConnectionMultiplexer multiplexer)
    {
        this.multiplexer = multiplexer;
    }

    public void DoYourJob() 
    {
        var database = multiplexer.GetDatabase(1);

        // your code        
    }
}

然后你可以轻松地模拟和测试它:

// Arrange
var mockMultiplexer = new Mock<IConnectionMultiplexer>();

mockMultiplexer.Setup(_ => _.IsConnected).Returns(false);

var mockDatabase = new Mock<IDatabase>();

mockMultiplexer
    .Setup(_ => _.GetDatabase(It.IsAny<int>(), It.IsAny<object>()))
    .Returns(mockDatabase.Object);

var cacheHandler = new RedisCacheHandler(mockMultiplexer.Object);

// Act
cacheHandler.DoYourJob();


// Assert
// your tests

答案 2 :(得分:0)

上面的答案中未包括的更详细的嘲笑数据库实例设置。我很难找到一个可行的示例来模拟IDatabase StringGet方法(例如,处理可选参数,使用RedisKey与字符串,使用RedisValue与字符串等)之类的简单操作,所以我想与我分享一下。这是对我有用的。

此测试设置:

var expected = "blah";
RedisValue expectedValue = expected;

mockDatabase.Setup(db => db.StringGet(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.Returns(expectedValue);

要影响此经过测试的方法调用返回的内容:

var redisValue = _connectionMultiplexer.GetDatabase().StringGet(key);