为什么这些 NSubstitute 参数匹配器不匹配?

问题描述 投票:0回答:1

我自己编写了一个非常基本的 MediatR 实现;了解它解决的问题。 (我冒昧地稍微改变了命名,因为 MediatR 并没有真正实现原始的 GoF Mediator 模式。请不要介意。) 我已经实现了 IServiceCollection 的扩展方法,该方法可以扫描程序集广告调用

IServiceCollection.AddTransient(IRequestHandler<SomeRequestType>, SomeRequestTypeHandler);
以获取所有
IRequestHandler<TRequest>
实现。

手动测试时,它就像一个魅力。但是,当添加以下自动化测试来断言为

AddTransient()
IRequestHandler<VoidDispatcherRequest>
调用
VoidDispatcherRequestHandler
时,测试将不会通过。 这是为什么?

    [Fact]
    public void When__Given__Then()
    {
        // Arrange
        var serviceCollection = Substitute.For<IServiceCollection>();

        // Act
        RequestDispatcherRegistrar.AddRequestDispatcher(serviceCollection);

        // Assert
        serviceCollection.Received()
            .AddTransient(Arg.Is<Type>(t => t == typeof(IRequestHandler<VoidDispatcherRequest>)),
                Arg.Is<Type>(t => t == typeof(VoidDispatcherRequestHandler)));
    }

产生以下错误:

System.ArgumentNullException: Value cannot be null. (Parameter 'serviceType')

System.ArgumentNullException
Value cannot be null. (Parameter 'serviceType')
   at System.ThrowHelper.Throw(String paramName)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(IServiceCollection services, Type serviceType, Type implementationType)
   at Presentation.Functional.UnitTest1.When__Given__Then() in [...]
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr).

以下是接口及其实现:

public interface IRequest : IBaseRequest { }

public interface IBaseRequest { }

public class VoidDispatcherRequest : IRequest { }

public interface IRequestHandler<in TRequest>
    where TRequest : IRequest
{
    Task HandleAsync(TRequest request, CancellationToken cancellationToken);
}

public class VoidDispatcherRequestHandler : IRequestHandler<VoidDispatcherRequest>
{
    public Task HandleAsync(VoidDispatcherRequest request, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

实际上,我更喜欢这样编写测试:

    [Fact]
    public void When__Given__Then()
    {
        // Arrange
        var serviceCollection = Substitute.For<IServiceCollection>();

        // Act
        RequestDispatcherRegistrar.AddRequestDispatcher(serviceCollection);

        // Assert
        serviceCollection.Received()
            .AddTransient(typeof(IRequestHandler<VoidDispatcherRequest>), typeof(VoidDispatcherRequestHandler));
    }

但这会产生以下错误:

NSubstitute.Exceptions.ReceivedCallsException: Expected to receive a call matching:

NSubstitute.Exceptions.ReceivedCallsException
Expected to receive a call matching:
    Add(ServiceType: Application.IRequestHandler`1[Presentation.Functional.VoidDispatcherRequest] Lifetime: Transient ImplementationType: Presentation.Functional.VoidDispatcherRequestHandler)
Actually received no matching calls.
Received 5 non-matching calls (non-matching arguments indicated with '*' characters):
    Add(*ServiceType: Application.IRequestHandler`2[Presentation.Functional.DispatcherRequest,Presentation.Functional.DispatcherResponse] Lifetime: Transient ImplementationType: Presentation.Functional.DispatcherRequestHandler*)
    [...]
    **Add(*ServiceType: Application.IRequestHandler`1[Presentation.Functional.VoidDispatcherRequest] Lifetime: Transient ImplementationType: Presentation.Functional.VoidDispatcherRequestHandler*)**
    [...]
    Add(*ServiceType: Application.IRequestDispatcher Lifetime: Transient ImplementationFactory: Application.IRequestDispatcher <AddRequestDispatcher>b__0_0(System.IServiceProvider)*)

   at NSubstitute.Core.ReceivedCallsExceptionThrower.Throw(ICallSpecification callSpecification, IEnumerable`1 matchingCalls, IEnumerable`1 nonMatchingCalls, Quantity requiredQuantity)
   at NSubstitute.Routing.Handlers.CheckReceivedCallsHandler.Handle(ICall call)
   at NSubstitute.Routing.Route.Handle(ICall call)
   at NSubstitute.Core.CallRouter.Route(ICall call)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at NSubstitute.Proxies.CastleDynamicProxy.ProxyIdInterceptor.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.ObjectProxy.Add(ServiceDescriptor item)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(IServiceCollection services, Type serviceType, Type implementationType)
   at Presentation.Functional.UnitTest1.When__Given__Then() in [...]
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

注意正确的调用实际上是如何列在所做的调用中的。

我知道项目名称目前不是最佳的。请不要介意。

c# .net xunit nsubstitute
1个回答
0
投票

您似乎正在尝试测试 IServiceCollection.AddTransient 是否在 RequestDispatcherRegistrar.AddRequestDispatcher 方法中正确调用。但是,在尝试验证这些调用时,您遇到了 NSubstitute 问题。让我们分解一下问题和潜在的解决方案。

NSubstitute 和 AddTransient 的问题 NSubstitute 对 IServiceCollection 的行为:

NSubstitute 的 IServiceCollection 模拟本身并不支持验证 AddTransient 调用的细节,尤其是当方法签名涉及类型参数时。

AddTransient 方法签名:

AddTransient 方法是 IServiceCollection 上的扩展方法,它在内部使用 ServiceDescriptor 调用 Add。当使用 NSubstitute 验证对 AddTransient 的调用时,确保验证中使用准确的方法签名和参数至关重要。

验证 AddTransient 调用的解决方案

  1. 使用带有自定义验证的 IServiceCollection 模拟 您需要验证是否使用正确的参数调用了 AddTransient。但是,由于 IServiceCollection 是一个具有扩展方法的接口,因此您可能必须验证正在添加的底层 ServiceDescriptor:

    [事实] 公共无效当__Given__Then() { // 安排 var serviceCollection = Substitute.For(); var serviceDescriptors = new List();

     // Use a callback to capture the ServiceDescriptors being added
     serviceCollection
         .When(x => x.Add(Arg.Any<ServiceDescriptor>()))
         .Do(x => serviceDescriptors.Add(x.Arg<ServiceDescriptor>()));
    
     // Act
     RequestDispatcherRegistrar.AddRequestDispatcher(serviceCollection);
    
     // Assert
     Assert.Contains(serviceDescriptors, sd =>
         sd.ServiceType == typeof(IRequestHandler<VoidDispatcherRequest>) &&
         sd.ImplementationType == typeof(VoidDispatcherRequestHandler) &&
         sd.Lifetime == ServiceLifetime.Transient);
    

    }

了解更多

© www.soinside.com 2019 - 2024. All rights reserved.