Mocking out expression with NSubstitute

こ雲淡風輕ζ 提交于 2020-01-02 06:11:44

问题


I have a interface that contains the following method signature:

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

Using Moq, I'm able to mock a specific call of 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 do 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 use NSubstitute, and not Moq. I tried using the same type of setup here:

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

However, when I do the following 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>>>() just to see if it works then, and then it returns "SecretAgentId" as expected. But I need to verify that it is called with the correct expression, and not just any expression.

So I need to know if it is possible to get this to work in NSubstitute, and if it is, how?


回答1:


I think that expressions are evaluated in NSubstitute depending on their closure scope, so the two expressions declarations are not identical. It looks like a bug to me, you may want to open an issue.

You can however take the expression out of the substitution declaration and it works 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"
}



回答2:


I can't remember exact syntax, so forgive me if this isn't A1 correct, and it is 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";
});



回答3:


As @Fordio already pointed out, Arg.Is<T>() is the way to go. You can use it to specify the conditions an argument must meet in order to provide the expected result. It is especially useful, when you don't have control of the argument's initialization and can't provide a proper reference to Returns() that would be valid for later method calls.

For instance you want to mock 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 of GetOrExecute() by itself:

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;
    }
}

In consequence following setup would not work, if you wanted to mock the cache and test only the RequestHandler, because you can't provide a reference to the parameter, which is initialized within 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's properties like this:

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


来源:https://stackoverflow.com/questions/26120821/mocking-out-expression-with-nsubstitute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!