c# - 如何让后备服务提供者返回编译时未知的类型参数的 Mock
问题描述
我正在为 Razor 组件编写 BUnit 测试,该组件已注入如下依赖项:
@inject IMyFirstService FirstService
@inject IMySecondService SecondService
@code {
// do stuff
}
对于我的测试,我创建了一个后备服务提供程序MoqServiceProvider
,用于注册我的模拟依赖项。 但我也希望后备服务提供者Moq<MyType>
默认为我没有明确模拟的任何类型提供一个实例。
后备服务提供者如下所示
public class MockServiceProvider : IServiceProvider
{
private readonly Dictionary<Type, object> services = new();
public void RegisterServices(params object[] serviceInstances)
{
this.services.Clear();
foreach (object serviceInstance in serviceInstances)
{
if (serviceInstance is Mock)
{
this.services.Add(serviceInstance.GetType().GetGenericArguments().First(), (serviceInstance as Mock).Object);
}
else
{
this.services.Add(serviceInstance.GetType(), serviceInstance);
}
}
}
public object GetService(Type serviceType)
{
if (this.services.TryGetValue(serviceType, out object service))
{
return service;
}
else
{
// I want to return a Mock of serviceType here
// something like return new Mock<serviceType>(), but I don't know how to do that
}
}
}
我在测试中这样使用它(我使用 AutoFixture 来提供测试参数):
@inherits TestContext
@code{
[Theory, AutoDomainData]
public void TestSomething(
Mock<IMyFirstService> myFirstService,
MockServiceProvider serviceProvider)
{
serviceProvider.RegisterServices(myFirstService);
Services.AddFallbackServiceProvider(serviceProvider);
var component = Render(@<MyComponent />);
// do stuff to test
}
}
如果我运行它,我会得到一个错误Cannot provide a value for property 'SecondService' on type 'MyComponent'
,因为我还没有注册一个Mock<IMySecondService>
with的实例MockServiceProvider
。
我如何MockServiceProvider
返回我没有明确注册的每种类型的模拟(类似return Mock<serviceType
)?我的一些 Razor 组件有很多依赖项,我不想注入那些对每个测试都无关紧要的组件。
解决方案
如果根服务提供者无法解析GetService
请求,则调用添加到 bUnit 的根服务提供者的后备服务提供者。考虑到这些信息,我们可以使用 Moq(或其他模拟框架)和一些反射技巧来创建一个后备服务提供者,这实际上只是实现IServiceProvider
接口的东西,它将使用 Moq 创建一个请求的模拟版本服务,当它的GetService
方法被调用时。
AutoMockingServiceProvider
该服务提供者将使用 Mock 创建一次请求服务类型的模拟,任何后续请求都将返回相同的类型(它们保存在mockedTypes
字典中)。
这样做的原因是您可以在测试和被测组件中检索相同类型的模拟实例,这允许您在测试中配置模拟。
下面的扩展方法可以很容易地从服务提供商GetMockedService
那里拉出一个。Mock<T>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Moq;
public class AutoMockingServiceProvider : IServiceProvider
{
private static readonly MethodInfo GenericMockFactory = typeof(Mock).GetMethods().First(x => x.Name == "Of");
private readonly Dictionary<Type, object> mockedTypes = new();
public object? GetService(Type serviceType) => GetMockedService(serviceType);
public object GetMockedService<T>() => GetMockedService(typeof(T));
public object GetMockedService(Type serviceType)
{
if (!mockedTypes.TryGetValue(serviceType, out var service))
{
var mockFactory = GenericMockFactory.MakeGenericMethod(serviceType);
service = mockFactory.Invoke(null, Array.Empty<object>())!;
mockedTypes.Add(serviceType, service);
}
return service;
}
}
internal static class ServiceProviderExtensions
{
public static Mock<T> GetMockedService<T>(this IServiceProvider services)
where T : class => Mock.Get<T>(services.GetService<T>()!);
}
注意:此代码不处理任何边缘情况,因此它可能不适用于所有情况,但应该作为一个很好的起点。
示例用法
假设我们有以下组件:
@inject IPerson Person
@Person.Name
这取决于这个接口:
public interface IPerson
{
public string Name { get; }
}
然后可以这样测试:
[Fact]
public void Test1()
{
using var ctx = new TestContext();
// Add the AutoMockingServiceProvider as the fallback service provider
ctx.Services.AddFallbackServiceProvider(new AutoMockingServiceProvider());
// Retrieves the mocked person from the service collection and configures it.
var mockedPerson = ctx.Services.GetMockedService<IPerson>();
mockedPerson.SetupGet(x => x.Name).Returns("Foo Bar");
// Render component
var cut = ctx.RenderComponent<MyComp>();
// Verify content
cut.MarkupMatches("Foo Bar");
}
这是使用 .NET 6 rc.1、Moq 4.16.1 和 bunit 1.2.49 测试的。
推荐阅读
- ruby-on-rails - 如何创建具有多个参数的自定义 rake 任务?
- flutter - 当用户使用颤振解锁屏幕时如何运行代码
- python - Python Selenium 在 div id 下遍历具有相同类名的 div
- javascript - 渲染的钩子比之前的渲染更多,只发生在一个组件中?
- c - gdb如何实现调用功能
- flutter - 在 Flutter 中使用列表视图滚动
- javascript - 带有对象的表头和正文
- android - 此项目使用 AndroidX 依赖项,但未启用“android.useAndroidX”属性
- jwt - SHA256、SHA1、SHA384 哈希到 JSON Web 令牌
- javascript - 获取具有相同类的所有输入值并用 % 符号替换它们作为“数字”?