我有一个包含以下方法签名的接口:
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中运行,如果是,怎么样?
答案 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);