NSubstitute - TestFixture 1在TestFixture 2中导致AmbiguousArgumentsException

时间:2014-11-07 16:11:49

标签: c# nunit nsubstitute

我正在使用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...,以防重载成为问题。
  • 我已经阅读了NSubstitute docs on argument matching,但我无法在那里找到解决方案。如果是必须为方法的int和字符串版本提供参数匹配器的情况,我不能弄清楚如何做到这一点。

实际上,我使用的测试只会调用值为0或1的GetConfig方法,所以我只能为这两个值提供Returns规范而不是完全使用匹配,但我想了解如何更普遍地解决这个问题。

4 个答案:

答案 0 :(得分:12)

不明确的论点是当NSubstitute将其当前正在使用的调用的参数与#34;参数匹配器的堆栈进行比较时#34;它有(每次调用Arg.Blah时,参数匹配器被添加到该堆栈中),并且它无法解析哪个参数在哪里。

通常这是由于调用blah(null, null)之类的,只有一个参数匹配器排队,但也可能是由于堆栈因为在外部使用arg匹配器而失去同步而引起的调用配置,或作为非虚方法的参数。

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

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

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

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

答案 1 :(得分:3)

I had similar errors是在我将Microsoft测试运行器切换到VSTest.Console时启动的(在MSTest.exe下运行时没有发生)。

正如David's answer中所建议的那样,错误是由调用带有Arg.*参数的非替代方法引起的。 Arg.Any已传递给实际的代码方法,即不使用ReturnsReceived相关方法调用的方法。

要扫描我的测试库以查找此类问题,我使用正则表达式搜索来查找Arg.Arg.后跟ReturnsReceived

(?=^.*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)