用NSubstitute嘲笑表达

时间:2014-09-30 12:25:57

标签: c# .net moq nsubstitute

我有一个包含以下方法签名的接口:

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

使用Moq,我能够像这样模拟这种方法的特定调用:

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

然后当我这样做时,打电话

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

Tt按照我的预期返回"SecretAgentId",所以一切都很好。

我的问题是,在我们的实际生产代码中,我们使用的是NSubstitute,而不是Moq。我尝试在这里使用相同类型的设置:

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

但是,当我在此处进行以下调用时

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

它返回&#34;&#34;而不是"SecretAgentId"

我尝试将c => c.SecretAgentId替换为Arg.Any<Expression<Func<Customer, string>>>()只是为了查看它是否有效,然后按预期返回"SecretAgentId"。但我需要验证是否使用正确的表达式调用它,而不仅仅是任何表达式。

所以我需要知道是否有可能让它在NSubstitute中运行,如果是,怎么样?

3 个答案:

答案 0 :(得分:3)

我认为表达式在NSubstitute中根据其闭包范围进行评估,因此两个表达式声明不相同。对我来说这看起来像个错误,你可能想要打开一个问题。

然而,你可以从替换声明中取出表达式并且它可以正常工作:

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

答案 1 :(得分:1)

我无法记住确切的语法,所以请原谅我,如果这不是A1正确的话,而且它有点kludgy但是......

当你尝试使用Arg.Any时,我相信你是在正确的轨道上,但尝试使用Arg.Is是这样的:

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 :(得分:0)

正如@Fordio指出的那样,Arg.Is<T>()是必经之路。您可以使用它来指定参数必须满足的条件才能提供预期的结果。当您无法控制参数的初始化并且无法提供对以后的方法调用有效的Returns()的正确引用时,它特别有用。

例如,您要模拟以下界面:

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

哪个类由另一个类自行初始化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;
    }
}

如果您想模拟缓存并仅测试RequestHandler,那么以下设置将无法工作,因为您无法提供对在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);

但是,您可以通过在参数的属性中指定条件来解决此问题,如下所示:

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