This is my test for my cache in program:
[Fact]
public async Task Cache_Works_Correctly()
{
const string testkey = "69";
const string testvalue = "Nice";
var mockDatabase = new Mock<IDatabase>();
mockDatabase.Setup<string>(_ => _.StringGet(CacheConstants.NamePrefix + testkey, CommandFlags.None)).Returns(testvalue);
var mockMultiplexer = new Mock<IConnectionMultiplexer>();
mockMultiplexer.Setup(_ => _.IsConnected).Returns(false);
mockMultiplexer
.Setup(_ => _.GetDatabase(It.IsAny<int>(), It.IsAny<object>()))
.Returns(mockDatabase.Object);
var cache = new CacheStore(mockMultiplexer.Object.GetDatabase(), new Mock<ILogger<CacheStore>>().Object);
Assert.Equal(testvalue, await cache.GetValueAsync<string>(testkey));
}
The problem appears in this row:
mockDatabase.Setup<string>(_ => _.StringGet(CacheConstants.NamePrefix + testkey, CommandFlags.None)).Returns(testvalue);
When I run the test, I get an Argument Exception:
Message:
System.ArgumentException : Unsupported expression: (string)_.StringGet((RedisKey)"SummaryAPI_69", CommandFlags.None)
Stack Trace:
ExpressionExtensions.Split(LambdaExpression expression, Boolean allowNonOverridableLastProperty) line 159
Mock.SetupRecursive[TSetup](Mock mock, LambdaExpression expression, Func`4 setupLast, Boolean allowNonOverridableLastProperty) line 645
Mock.Setup(Mock mock, LambdaExpression expression, Condition condition) line 500
Mock`1.Setup[TResult](Expression`1 expression) line 452
UniversalCacheTests.Cache_Works_Correctly() line 23
--- End of stack trace from previous location ---
It seems that Moq can’t setup IDatabase’s function StringGet() but it works fine with ToString(), for example. Is there any way to do what I want?
This is my CacheStore.GetValueAsync function which I want to test by mocking IDatabase:
public async Task<TValue?> GetValueAsync<TValue>(string key)
{
key = CacheConstants.NamePrefix + key;
this._logger.LogInformation($"Getting {key} from cache.");
var jsonData = await Task.Run(() => this._database.StringGet(key));
if (jsonData == RedisValue.Null)
{
return default;
}
return JsonSerializer.Deserialize<TValue>(jsonData!);
}
I`ve tried to check if I could setup IDatabase’s another functions, and that worked! Looks that Moq just can’t work with that specific functions. Looking into the code, problem appears it Moq .Split() function. It is written that it
Splits an expression such as m => m.A.B(x).C[y] = z into a chain of parts
that can be set up one at a time:
m => m.A
... => ....B(x)
... => ....C
... => ...[y] = z
Why is IDatabase.StringGet problematic for it? Idk
Here is how it looks in code:
internal static Stack<MethodExpectation> Split(this LambdaExpression expression, bool allowNonOverridableLastProperty = false)
{
Debug.Assert(expression != null);
var parts = new Stack<MethodExpectation>();
Expression remainder = expression.Body;
while (CanSplit(remainder))
{
Split(remainder, out remainder, out var part, allowNonOverridableLastProperty: allowNonOverridableLastProperty && parts.Count == 0);
parts.Push(part);
}
if (parts.Count > 0 && remainder is ParameterExpression)
{
return parts;
}
else
{
throw new ArgumentException(
string.Format(
CultureInfo.CurrentCulture,
Resources.UnsupportedExpression,
remainder.ToStringFixed()));
}
2
Answers
You can try setting up the mock differently to match wider range of parameters and see where the problem is.
So change this
to
this will allow you to mock any
StringGet
invocation and see if that takes any effect.If so, then change back one parameters, try again, change again, until you find the problem 🙂
This happens because
StringGet
is defined like this:but the test setup is defined like this:
Notice the
string
type parameter, which indicates to the C# compiler that the return value for theStringGet
method should be astring
, even though it’s clearlyRedisValue
.Change the
Setup
by removing the type parameter:Then the failure disappears.