如何检查指定服务是否已通过Ninject内核实例化?
解决Ninject集成测试中仅清理已实例化服务的问题
要实现你想要的「判断服务是否已被内核实例化,并在TearDown中针对性清理」的需求,Ninject本身没有直接提供HasBeenInstantiated方法,但我们可以通过自定义激活策略来跟踪实例化记录,再封装扩展方法实现你的预期功能。下面是具体的实现步骤:
1. 实现实例跟踪的激活策略
首先创建一个InstanceTrackingStrategy类,它会在Ninject激活实例时自动记录对应的服务类型和实例本身:
using Ninject.Activation; using Ninject.Components; using System; using System.Collections.Generic; using System.Linq; public class InstanceTrackingStrategy : NinjectComponent, IActivationStrategy { // 存储已实例化的服务类型与对应实例的映射 private readonly Dictionary<Type, List<object>> _instantiatedInstances = new(); public void Activate(IContext context, InstanceReference reference) { var serviceType = context.Request.Service; if (!_instantiatedInstances.ContainsKey(serviceType)) { _instantiatedInstances[serviceType] = new List<object>(); } _instantiatedInstances[serviceType].Add(reference.Instance); } public void Deactivate(IContext context, InstanceReference reference) { // 这里留空即可,我们主要在测试的TearDown阶段统一处理清理 } // 检查指定服务类型是否有实例被创建过 public bool HasBeenInstantiated<T>() { return _instantiatedInstances.ContainsKey(typeof(T)) && _instantiatedInstances[typeof(T)].Any(); } // 获取指定服务类型的所有实例 public IEnumerable<T> GetInstantiatedInstances<T>() { if (_instantiatedInstances.TryGetValue(typeof(T), out var instances)) { return instances.Cast<T>(); } return Enumerable.Empty<T>(); } // 统一清理所有已实例化的可释放对象 public void CleanupAllInstances() { foreach (var instances in _instantiatedInstances.Values) { foreach (var instance in instances) { if (instance is IDisposable disposable) { disposable.Dispose(); } } } _instantiatedInstances.Clear(); } }
2. 封装Ninject扩展方法
为了让测试代码更简洁,我们给IKernel添加扩展方法,直接调用跟踪策略的功能:
using Ninject; public static class NinjectInstanceTrackingExtensions { public static bool HasBeenInstantiated<T>(this IKernel kernel) { var trackingStrategy = kernel.Components.Get<InstanceTrackingStrategy>(); return trackingStrategy?.HasBeenInstantiated<T>() ?? false; } public static IEnumerable<T> GetInstantiatedInstances<T>(this IKernel kernel) { var trackingStrategy = kernel.Components.Get<InstanceTrackingStrategy>(); return trackingStrategy?.GetInstantiatedInstances<T>() ?? Enumerable.Empty<T>(); } public static void CleanupInstantiatedInstances(this IKernel kernel) { var trackingStrategy = kernel.Components.Get<InstanceTrackingStrategy>(); trackingStrategy?.CleanupAllInstances(); } }
3. 在测试中使用
现在你可以在集成测试中按照预期的方式使用,以xUnit为例:
using Ninject; using Xunit; public class MyIntegrationTests : IDisposable { private readonly IKernel _kernel; public MyIntegrationTests() { _kernel = new StandardKernel(); // 将跟踪策略添加到内核组件中 _kernel.Components.Add<IActivationStrategy, InstanceTrackingStrategy>(); // 绑定测试所需的服务 _kernel.Bind<IMyService>().To<MyService>(); } [Fact] public void TestServiceWorkflow() { // 初始状态下,IMyService未被实例化 Assert.False(_kernel.HasBeenInstantiated<IMyService>()); // 获取服务实例,触发实例化 var myService = _kernel.Get<IMyService>(); // 执行测试逻辑... // 此时IMyService已被实例化 Assert.True(_kernel.HasBeenInstantiated<IMyService>()); } // TearDown逻辑(xUnit通过IDisposable实现,其他框架可使用[TearDown]属性) public void Dispose() { // 仅当IMyService被实例化过,才执行自定义清理逻辑 if (_kernel.HasBeenInstantiated<IMyService>()) { foreach (var service in _kernel.GetInstantiatedInstances<IMyService>()) { // 比如调用服务的重置方法 if (service is MyService concreteService) { concreteService.ResetTestState(); } } } // 统一清理所有可释放的实例,再销毁内核 _kernel.CleanupInstantiatedInstances(); _kernel.Dispose(); } } // 示例服务定义 public interface IMyService { void ResetTestState(); } public class MyService : IMyService, IDisposable { public void ResetTestState() { // 测试专用的状态重置逻辑 } public void Dispose() { // 释放资源的逻辑 } }
关键说明
- 激活策略会自动捕获所有通过
kernel.Get<T>()创建的实例,不会遗漏任何Ninject管理的对象。 - 扩展方法的调用方式完全符合你预期的
kernel.HasBeenInstantiated<IMyService>()写法,非常直观。 - 在TearDown中,你可以选择针对特定服务做自定义清理,也可以调用通用方法统一释放所有可回收实例,灵活度很高。
内容的提问来源于stack exchange,提问作者BG100
相关产品推荐
相关产品推荐

