问题
I am working to mock up behaviors related to the StackExchange.Redis library, but can't figure out how to properly mock the sealed classes it uses. A specific example is in my calling code I'm doing something like this:
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)
{
The key line in there is _cache.Multiplexer.IsConnected where I'm checking to make sure I have a valid connection before using the cache. So in my tests I want to mock up this behavior with something like this:
_mockCache = new Mock<IDatabase>();
_mockCache.Setup(cache => cache.Multiplexer.IsConnected).Returns(false);
However, while that code compiles just fine, I get this error when running the test:
I have also tried mocking the multiplexer class itself, and providing that to my mocked cache, but I run into the fact the multiplexer class is sealed:
_mockCache = new Mock<IDatabase>();
var mockMultiplexer = new Mock<ConnectionMultiplexer>();
mockMultiplexer.Setup(c => c.IsConnected).Returns(false);
_mockCache.Setup(cache => cache.Multiplexer).Returns(mockMultiplexer.Object);
...but that results in this error:
Ultimately I want to control whether that property is true or false in my tests, so is there a correct way to mock up something like this?
回答1:
The best approach in my opinion is to wrap all of your Redis interaction in your own class and interface. Something like CacheHandler : ICacheHandler
and ICacheHandler
. All of your code would only ever speak to ICacheHandler
.
This way, you eliminate a hard dependency on Redis (you can swap out the implementation of ICacheHandler
as you please). You can also mock all interaction with your caching layer because it's programmed against the interface.
You should not test StackExchange.Redis
directly - it is not code you've written.
回答2:
Use the interface IConnectionMultiplexer instead of the concrete class ConnectionMultiplexer in your own class.
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
}
}
Then you could easily mock and test it:
// 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
回答3:
Not included in the above answer is the more detailed Setup of the mockDatabase instance. I struggled a little bit finding a working example of something as simple as mocking the IDatabase StringGet method (e.g., handling of optional parameters, using RedisKey vs string, using RedisValue vs string, etc.), so thought I would share. Here is what worked for me.
This test setup:
var expected = "blah";
RedisValue expectedValue = expected;
mockDatabase.Setup(db => db.StringGet(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>()))
.Returns(expectedValue);
To affect what is returned by this tested method call:
var redisValue = _connectionMultiplexer.GetDatabase().StringGet(key);
来源:https://stackoverflow.com/questions/28325870/how-to-use-moq-to-mock-up-the-stackexchange-redis-connectionmultiplexer-class