我正在使用NUnit和NSubstitute编写C#单元测试。我正在测试一个类,它将尝试从实现以下接口的配置提供程序中检索对象:
public interface IConfigProvider<T> {
T GetConfig(int id);
T GetConfig(string id);
}
正在测试的类只使用GetConfig
的int版本,所以在SetUpFixture中我执行以下操作来设置一个总是返回相同虚拟对象的模拟配置提供程序:
IConfigProvider<ConfigType> configProvider = Substitute.For<IConfigProvider<ConfigType>>();
configProvider.GetConfig(Arg.Any<int>()).Returns<ConfigType>(new ConfigType(/* args */);
如果TestFixture是唯一运行的TestFixture,则运行绝对正常。但是,在同一个程序集中的不同TestFixture中,我检查接收到的调用如下:
connection.Received(1).SetCallbacks(Arg.Any<Action<Message>>(), Arg.Any<Action<long>>(), Arg.Any<Action<long, Exception>>());
如果在配置提供程序测试之前运行了这些Received
测试,那么配置测试在SetUpFixture中失败并出现AmbiguousArgumentsException:
Here.Be.Namespace.ProfileManagerTests+Setup (TestFixtureSetUp):
SetUp : NSubstitute.Exceptions.AmbiguousArgumentsException : Cannot determine argument specifications to use.
Please use specifications for all arguments of the same type.
at NSubstitute.Core.Arguments.NonParamsArgumentSpecificationFactory.Create(Object argument, IParameterInfo parameterInfo, ISuppliedArgumentSpecifications suppliedArgumentSpecifications)
at System.Linq.Enumerable.<SelectIterator>d__7`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at NSubstitute.Core.Arguments.MixedArgumentSpecificationsFactory.Create(IList`1 argumentSpecs, Object[] arguments, IParameterInfo[] parameterInfos)
at NSubstitute.Core.Arguments.ArgumentSpecificationsFactory.Create(IList`1 argumentSpecs, Object[] arguments, IParameterInfo[] parameterInfos, MatchArgs matchArgs)
at NSubstitute.Core.CallSpecificationFactory.CreateFrom(ICall call, MatchArgs matchArgs)
at NSubstitute.Routing.Handlers.RecordCallSpecificationHandler.Handle(ICall call)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at NSubstitute.Routing.Route.Handle(ICall call)
at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.IConfigProvider`1Proxy.GetConfig(Int32 id)
at Here.Be.Namespace.ProfileManagerTests.Setup.DoSetup()
让我感到困惑的是,即使在测试运行之间我也能观察到这种效果 - 如果我使用NUnit GUI单独运行Received
测试,然后单独运行配置测试,配置测试将失败。如果我再次立即运行配置测试,它们将通过。
我尝试的事情:
configProvider.GetConfig(Arg.Any<string>()).Returns...
,以防重载成为问题。实际上,我使用的测试只会调用值为0或1的GetConfig
方法,所以我只能为这两个值提供Returns
规范而不是完全使用匹配,但我想了解如何更普遍地解决这个问题。
答案 0 :(得分:12)
不明确的论点是当NSubstitute将其当前正在使用的调用的参数与#34;参数匹配器的堆栈进行比较时#34;它有(每次调用Arg.Blah
时,参数匹配器被添加到该堆栈中),并且它无法解析哪个参数在哪里。
通常这是由于调用blah(null, null)
之类的,只有一个参数匹配器排队,但也可能是由于堆栈因为在外部使用arg匹配器而失去同步而引起的调用配置,或作为非虚方法的参数。
版本1.8.0(在您提出问题后发布)包括对后一种情况的略微改进的检测,因此可能值得尝试。
除此之外,我已经有过几次这个问题并使用了以下(痛苦)的方法。
Arg.xyz
调用。确保将其用作呼叫配置的一部分。有时可以通过注释掉行或用其他值替换arg匹配器来确定哪个调用有问题。有时问题可能是由于之前的夹具造成的,所以您可能需要锻炼以前的夹具并在那里进行探索。 :(
答案 1 :(得分:3)
I had similar errors是在我将Microsoft测试运行器切换到VSTest.Console
时启动的(在MSTest.exe
下运行时没有发生)。
正如David's answer中所建议的那样,错误是由调用带有Arg.*
参数的非替代方法引起的。 Arg.Any
已传递给实际的代码方法,即不使用Returns
或Received
相关方法调用的方法。
要扫描我的测试库以查找此类问题,我使用正则表达式搜索来查找Arg.
但Arg.
后跟Returns
或Received
{ p>
(?=^.*Arg.*$)(?=^((?!Arg.*\.Returns).)*$)^((?!\.Received\(.*Arg.).)*$
它不是防弹过滤器(例如它不排除多行语句),但它有助于减少要检查的呼叫数量。
答案 2 :(得分:0)
更改我的测试顺序。不是很好的答案,但工作 - 尝试!
答案 3 :(得分:0)
这是我最终使用的正则表达式(就我而言,在 Rider 中,但它是标准的 .Net 正则表达式)。它可以很好地处理多行语句。我确实出现了一些误报,其中附加了 Returns()
次调用,但不到总使用次数的 1%。
它不处理 When()
/Do()
或 WithAnyArgs
变体,但到目前为止,这些是我特定代码库中的少数用法。
^(?!\s*\.)[\w\s_\.]+\.(?!DidNotReceive\(\)\s*)(?!Received\(\d*\)\s*)[\w_]+\((((Arg.Any<.*?>\(\))|(Arg.Is(<.*?>)?\(.*\)))[,\s]*)+\)(?!\s*\.Return)(?!\s*?\.Throw)