NSubstitute - TestFixture 1 вызывает исключение AmbiguousArgumentsException в TestFixture 2

Я пишу блок-тесты С#, используя NUnit и NSubstitute. Я тестирую класс, который попытается извлечь объекты из поставщика конфигурации, реализующего следующий интерфейс:

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

Проверяемый класс использует только версию int GetConfig, поэтому в SetUpFixture я делаю следующее, чтобы настроить посредника конфигурации, который всегда будет возвращать один и тот же фиктивный объект:

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

Это работает абсолютно нормально, если 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 для запуска тестов Received, а затем запускаю только тесты конфигурации, тесты конфигурации не сработают. Если я затем снова запустим тесты конфигурации, они пройдут.

Вещи, которые я пробовал:

  • Добавление configProvider.GetConfig(Arg.Any<string>()).Returns... также в случае, если проблема перегрузки была проблемой.
  • Я прочитал документы NSubstitute по сопоставлению аргументов, но я не могу найти там решение. Если это случай, когда необходимо предоставить аргументы для обеих версий метода int и string, я не могу решить, как это сделать.

Как это бывает, тесты, которые я использую, будут когда-либо вызывать метод GetConfig со значениями 0 или 1, поэтому я могу просто предоставить спецификации Returns для этих двух значений и не использовать совпадение вообще, но Я хочу понять, как это сделать в целом.

Ответ 1

Неоднозначные аргументы - это когда NSubstitute сравнивает аргументы с вызовом, с которым он в настоящее время работает, в стек "аргументов", которые он имеет (каждый раз, когда вызывается Arg.Blah, в этот стек добавляется сопоставление аргументов) и он не может решить, какой аргумент идет туда.

Обычно это вызвано вызовом типа blah(null, null), с одиночным совпадением аргументов в очереди, но также может быть вызвано тем, что стек выходит из синхронизации из-за использования сопоставления аргументов вне конфигурации вызова, или в качестве аргумента для не виртуального метода.

Версия 1.8.0 (выпущенная после вашего вопроса) включает в себя несколько улучшенное обнаружение последнего случая, поэтому, возможно, стоит попробовать.

Кроме того, у меня была эта проблема несколько раз и я использовал следующий (болезненный) подход.

  • выполните тест отдельно и убедитесь, что он проходит
  • определите, какой тест запускается немедленно (обычно можно догадаться, но тестовые журналы могут помочь здесь) и запускать только эти два теста. Подтвердите, что он не работает.
  • Ищите любые вызовы Arg.xyz, которые могли бы поставить очередь на совпадение аргументов в любом тесте. Убедитесь, что он используется как часть конфигурации вызова. Иногда разработка того, какой вызов является проблематичным, может быть выполнена путем комментирования строк или замены аргументов arg другими значениями.
  • Убедитесь, что нет вызовов виртуальных методов, которые запутывают NSubstitute.

Иногда проблема может быть связана с предыдущим крепежом, поэтому вам может потребоваться тренировка предыдущего прибора и его исследование.: (

Ответ 2

I имел схожие ошибки, которые начались, когда я переключил тестер Microsoft на VSTest.Console (они не выполнялись при работе под MSTest.exe).

Как было сказано в ответе Дэвида, ошибки были вызваны вызовами незанятых методов с параметрами Arg.*. Arg.Any были переданы фактическим методам кода, которые вызываются без Returns или Received соответствующих методов.

Чтобы сканировать мою тестовую библиотеку для таких проблем, я использовал поиск с регулярным выражением для поиска строк с Arg., но не Arg., следующих за Returns или предшествующих Received

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

Это не защищенный от вирусов фильтр (например, он не исключает многострочные заявления), но он помогает уменьшить количество вызовов для проверки.

Ответ 3

Сменился порядок моих тестов. Не большой ответ, но работали - попробуйте!