You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Simple Injector拦截所有实例以实现集成测试?

Simple Injector集成测试中监控内部服务调用的解决方案

方案1:用FakeItEasy包装真实实例替换目标服务

这种方式直接替换容器中目标服务的注册,让所有依赖该服务的地方(包括隐式创建的依赖)都使用带Spy的包装实例,从而记录所有调用。

  • 步骤:
    1. 先注册服务的真实实现
    2. 在测试环境下,用RegisterConditional替换为FakeItEasy创建的Spy包装器,包装真实实例
    3. 将Spy实例保存到测试类的可访问字段中,用于后续验证调用

示例代码:

// 基础容器配置:注册真实服务
var container = new Container();
container.Register<IMyService, MyRealService>(Lifestyle.Scoped);

// 测试环境下替换为Spy包装
bool isTesting = true; // 测试时设为true
if (isTesting)
{
    container.RegisterConditional(typeof(IMyService),
        context =>
        {
            // 获取真实实例
            var realService = context.GetInstance<MyRealService>();
            // 创建包装真实实例的Spy,保留原有逻辑同时记录调用
            var serviceSpy = A.Fake<IMyService>(opt => opt.Wrapping(realService));
            // 保存Spy到测试类的静态字段或测试实例字段
            TestContext.MyServiceSpy = serviceSpy;
            return serviceSpy;
        },
        Lifestyle.Scoped,
        // 条件:所有请求都替换
        context => true);
}

// 测试验证部分
A.CallTo(() => TestContext.MyServiceSpy.Process(A<RequestModel>.That.Matches(r => r.Id == 123)))
  .MustHaveHappenedOnceExactly();

方案2:增强ApplyInterceptor获取服务类型

如果坚持使用拦截器方案,可以通过自定义拦截器传递服务类型,解决ApplyInterceptor中无法获取服务类型的问题。

  • 步骤:
    1. 实现带服务类型参数的自定义拦截器,包装核心拦截逻辑(比如FakeItEasy的调用记录)
    2. 在ApplyInterceptor注册时,将当前的服务类型传递给自定义拦截器

示例代码:

// 自定义拦截器,携带服务类型信息
public class TrackedInterceptor : IInterceptor
{
    public Type ServiceType { get; }
    private readonly IInterceptor _innerInterceptor;

    public TrackedInterceptor(Type serviceType, IInterceptor innerInterceptor)
    {
        ServiceType = serviceType;
        _innerInterceptor = innerInterceptor;
    }

    public void Intercept(IInvocation invocation)
    {
        // 此处可访问ServiceType,用于区分不同服务的调用
        Console.WriteLine($"监控到{ServiceType.Name}的{invocation.Method.Name}方法调用");
        _innerInterceptor.Intercept(invocation);
    }
}

// FakeItEasy拦截器实现,用于记录调用
public class FakeItEasyInterceptor : IInterceptor
{
    private readonly object _target;

    public FakeItEasyInterceptor(object target) => _target = target;

    public void Intercept(IInvocation invocation)
    {
        // 用FakeItEasy拦截调用并记录
        var fake = A.Fake(invocation.Target.GetType(), opt => opt.Wrapping(_target));
        var method = fake.GetType().GetMethod(invocation.Method.Name);
        method.Invoke(fake, invocation.Arguments);
        // 这里可以自定义记录逻辑,比如保存调用参数到测试上下文
    }
}

// 容器注册拦截器
container.ApplyInterceptor(
    typeof(IMyService),
    (context, instance) => new TrackedInterceptor(
        context.ServiceType,
        new FakeItEasyInterceptor(instance)));

方案3:直接替换InstanceProducer的实例

通过Simple Injector的InstanceProducer直接替换目标服务的实例,确保所有依赖该服务的地方都使用Spy实例。

示例代码:

var container = new Container();
container.Register<IMyService, MyRealService>(Lifestyle.Scoped);

// 获取目标服务的InstanceProducer
var producer = container.GetRegistration(typeof(IMyService)).Registration;
// 创建真实实例并包装为Spy
var realInstance = producer.BuildInstance();
var serviceSpy = A.Fake<IMyService>(opt => opt.Wrapping(realInstance));
// 替换Producer中的实例
producer.ReplaceInstance(serviceSpy);

// 测试验证
A.CallTo(() => serviceSpy.DoWork(A<string>.That.IsEqualTo("test"))).MustHaveHappened();

内容的提问来源于stack exchange,提问作者Marc Wittke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 21:50:22