Mock expression with NSubstitute

I have an interface that contains the following method signature:

TResult GetValue<T, TResult>(object key, Expression<Func<T, TResult>> property) where T : class;

      

Using Moq, I can mock a specific call to this method like this:

var repo = new Mock<IRepository>();
repo.Setup(r => r.GetValue<Customer, string>("SomeCustomerId", c => c.SecretAgentId)).Returns("SecretAgentId");

      

Then when I make this call

repo.Object.GetValue<Customer, string>("SomeCustomerId", c => c.SecretAgentId);

      

Tt returns "SecretAgentId"

as I expect, so everything looks fine.

My problem is that in our real production code we are using NSubstitute and not Moq. I tried using the same setup type here:

var repo = Substitute.For<ICrmRepository>();
repo.GetValue<Customer, string>("SomeCustomerId", c => c.SecretAgentId).Returns("SecretAgentId");

      

However, when I make the next call here

repo.GetValue<Customer, string>("SomeCustomerId", c => c.SecretAgentId);

      

It returns "" instead of "SecretAgentId"

I tried replacing c => c.SecretAgentId

with Arg.Any<Expression<Func<Customer, string>>>()

to see if it works then and then returns "SecretAgentId"

as expected. But I need to check that it is being called with the correct expression, not just any expression.

So what I need to know is if it can be made to work in NSubstitute, and if so, how?

+4


source to share


3 answers


I think that expressions are evaluated in NSubstitute based on their scope, so the declarations of the two expressions are not identical. It looks like an error, you can open the problem.

However, you can extract the expression from the substitution declaration and work correctly:



private static void Main(string[] args)
{
    Expression<Func<string, string>> myExpression =  s => s.Length.ToString();
    var c = Substitute.For<IRepo>();
    c.GetValue<string, string>("c", myExpression).Returns("C");

    var result = c.GetValue<string, string>("c", myExpression); // outputs "C"
}

      

+5


source


I can't remember the exact syntax, so forgive me if this is not correct A1 and it's a bit kludgy, but ...

I believe you were on the right track when you tried Arg.Any, however try using Arg.Is like this:



Arg.Is<Expression<Func<Customer, string>>>(x => { 
    var m = ((Expression)x).Body as MemberExpression;
    var p = m.Member as PropertyInfo;
    return p.Name == "SecretAgentId";
});

      

+2


source


As @Fordio already pointed out Arg.Is<T>()

is the way to go. You can use it to specify conditions that an argument must meet to provide the expected result. This is especially useful when you have no control over the initialization of the argument and cannot provide the correct reference to Returns()

be valid for subsequent method calls.

For example, you want to model the following interface:

 public interface IQueryCache
 {
     TResult GetOrExecute<TQuery, TResult>(TQuery query, Func<TResult> func)
         where TQuery : IQuery<TResult>
         where TResult : class;
 }

      

Which is used by another class that initializes the parameters itself GetOrExecute()

:

public class RequestHandler
{
    private readonly IQueryCache _cache;
    private readonly IRepository _repository;

    public RequestHandler(IRepository repository, IQueryCache cache)
    {
        _repository = repository;
        _cache = cache;
    }

    public TResult GetResult(Guid userId)
    {
        var query = new Query() { UserId = userId };
        var result = _cache.GetOrExecute(query, () => _repository.GetUserItems(query));
        return result;
    }
}

      

Hence, the following setup will not work if you want to mock the cache and test just RequestHandler

because you cannot provide a reference to a parameter that is initialized to GetRestult()

:

var repository = Substitute.For<IRepository>();
var cache = Substitute.For<IQueryCache>();

repository.GetUserItems(query).Returns(expected);
cache.GetOrExecute(query, () => repository.GetUserItems(query)).Returns(expected);

var handler = new RequestHandler(repository, cache);

      

However, you can work around this by specifying conditions on the argument properties, for example:

cache.GetOrExecute(Arg.Is<Query>(q => q.UserId == "value"), Arg.Any<Func<IEnumerable<TResult>>>()).Returns(expected);

      

0


source







All Articles