skip to Main Content

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


  1. You can try setting up the mock differently to match wider range of parameters and see where the problem is.

    So change this

    mockDatabase
        .Setup<string>(_ => _.StringGet(CacheConstants.NamePrefix + testkey, CommandFlags.None))
        .Returns(testvalue);
    

    to

    mockDatabase
        .Setup<string>(_ => _.StringGet(It.IsAny<string>(), It.IsAny<CommandFlags>()))
        .Returns(testvalue);
    

    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 🙂

    Login or Signup to reply.
  2. This happens because StringGet is defined like this:

    RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None);
    

    but the test setup is defined like this:

    mockDatabase.Setup<string>(_ => _.StringGet( //...
    

    Notice the string type parameter, which indicates to the C# compiler that the return value for the StringGet method should be a string, even though it’s clearly RedisValue.

    Change the Setup by removing the type parameter:

    mockDatabase.Setup(_ => _.StringGet( //...
    

    Then the failure disappears.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search