用NSubstitute模拟表达式
本文关键字:表达式 模拟 NSubstitute | 更新日期: 2023-09-27 18:25:08
我有一个包含以下方法签名的接口:
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);
它返回"而不是"SecretAgentId"
我试着用Arg.Any<Expression<Func<Customer, string>>>()
替换c => c.SecretAgentId
,只是想看看它当时是否有效,然后它按预期返回"SecretAgentId"
。但我需要验证它是用正确的表达式调用的,而不仅仅是任何表达式。
所以我需要知道是否有可能在NSubstitute中实现这一点,如果有,如何实现?
我认为表达式在NSubstitute中的求值取决于它们的闭包范围,所以这两个表达式声明是不相同的。在我看来,这就像一个bug,你可能想打开一个问题。
然而,您可以将表达式从替换声明中删除,并且它可以正确工作:
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"
}
我记不清确切的语法,所以如果这不是A1正确的,请原谅我,它有点笨拙,但。。。
我相信当你尝试Arg时,你是在正确的轨道上。然而,尝试使用Arg。是这样的:
Arg.Is<Expression<Func<Customer, string>>>(x => {
var m = ((Expression)x).Body as MemberExpression;
var p = m.Member as PropertyInfo;
return p.Name == "SecretAgentId";
});
正如@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);