NSubstitute – TestFixture 1在TestFixture 2中导致AmbiguousArgumentsException

我正在使用NUnit和NSubstitute编写C#unit testing。 我正在测试一个类,它将尝试从实现以下接口的配置提供程序中检索对象:

public interface IConfigProvider { T GetConfig(int id); T GetConfig(string id); } 

正在测试的类只使用了GetConfig的int版本,因此在SetUpFixture中我执行以下操作来设置一个总是返回相同虚拟对象的模拟配置提供程序:

 IConfigProvider configProvider = Substitute.For<IConfigProvider>(); configProvider.GetConfig(Arg.Any()).Returns(new ConfigType(/* args */); 

如果TestFixture是唯一运行的TestFixture,则运行绝对正常。 但是,在同一个程序集中的不同TestFixture中,我检查接收到的调用如下:

 connection.Received(1).SetCallbacks(Arg.Any<Action>(), Arg.Any<Action>(), Arg.Any<Action>()); 

如果这些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.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()).Returns...如果重载是问题,也configProvider.GetConfig(Arg.Any()).Returns...
  • 我已经阅读了关于参数匹配的NSubstitute文档 ,但我找不到解决方案。 如果是必须为方法的int和字符串版本提供参数匹配器的情况,我无法弄清楚如何做到这一点。

碰巧,我正在使用的测试只会调用值为0或1的GetConfig方法,所以我只能为这两个值提供Returns规范而根本不使用匹配,但我想了解如何修复这更普遍。

不明确的论点是当NSubstitute将它正在使用的调用的参数与它所拥有的“参数匹配器”的堆栈进行比较时(每次调用Arg.Blah ,将参数匹配器添加到该堆栈中),它是无法解决哪个参数在哪里。

通常这是由于调用像blah(null, null) ,单个参数匹配器排队,但也可能是由于在调用配置之外使用arg匹配器而导致堆栈失去同步,或作为非虚方法的参数。

版本1.8.0(在您的问题之后发布)包括对后一种情况的略微改进的检测,因此可能值得尝试。

除此之外,我有几次这个问题,并使用了以下(痛苦)的方法。

  • 单独运行测试并确保它通过
  • 找出哪些测试立即运行(通常可以猜测,但测试日志可以在这里帮助),并运行这两个测试。 确认失败。
  • 查找任何可能在任一测试中排队参数匹配器的Arg.xyz调用。 确保将其用作呼叫配置的一部分。 有时可以通过注释掉行或用其他值替换arg匹配器来确定哪个调用有问题。
  • 确保没有调用非虚拟方法使NSubstitute混淆。

有时问题可能是由于之前的夹具造成的,因此您可能需要锻炼以前的夹具并在那里进行探索。 🙁

当我将Microsoft测试运行器切换到VSTest.Console时,我遇到了类似的错误 (在MSTest.exe下运行时没有发生MSTest.exe )。

正如David在答案中所建议的那样,错误是由使用Arg.*参数调用非替代方法引起的。 Arg.Any被传递给实际的代码方法,在没有ReturnsReceived相关方法的情况下调用。

要扫描我的测试库以查找此类问题,我使用正则表达式搜索来查找带有Arg.Arg. 但不是Arg. 跟随Returns或以Received

 (?=^.*Arg.*$)(?=^((?!Arg.*\.Returns).)*$)^((?!\.Received\(.*Arg.).)*$ 

它不是防弹filter(例如,它不排除多行语句),但它有助于减少检查的呼叫数量。

改变我的测试顺序。 不是很好的答案,但工作 – 尝试!