NSubstitute декоратор интерфейса
Редактировал(а) Alexandr Fokin 2026/03/18 15:24
| Пример создание тестового декоратора, в котором в одном методе задается кастомное поведение, а другие методы пробрасывают поведение исходной реализации. При этом интерфейс может быть не доступен напрямую (private). | |
| Reflection | Рефлексия, DI IOC AOP | |
| public static void RegistryDecorator(IServiceCollection services) { var sourceRegistration = services.First( e => e.ServiceType.FullName == "IService"); services.Remove(sourceRegistration); services.Add(new ServiceDescriptor( sourceRegistration.ImplementationType, sourceRegistration.ImplementationType, ServiceLifetime.Singleton)); services.Add(new ServiceDescriptor( sourceRegistration.ServiceType, (s) => BuildDecorator( sourceRegistration, s.GetRequiredService(sourceRegistration.ImplementationType)), ServiceLifetime.Singleton)); } private static object BuildDecorator( ServiceDescriptor descriptor, object source) { static IEnumerable<MethodInfo> GetInterfaceMethodsRecursive(Type type) { foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)) { yield return method; } if (type.IsInterface) { foreach (var iface in type.GetInterfaces()) { foreach (var method in GetInterfaceMethodsRecursive(iface)) { yield return method; } } } } static object[] CreateFakeParameters(MethodInfo method) { var parameters = method.GetParameters(); var result = new List<object>(parameters.Length); foreach (var elem in parameters) { var type = !elem.ParameterType.IsByRef ? elem.ParameterType : elem.ParameterType.GetElementType(); var value = !type.IsValueType ? null : Activator.CreateInstance(type); result.Add(value); } return result.ToArray(); } var decorator = Substitute.For( new[] { descriptor.ServiceType }, Array.Empty<object>()); var methodsForDecoration = GetInterfaceMethodsRecursive(descriptor.ServiceType) .ToHashSet(); // Подменяем поведение нужного метода. // string Action1(string text) var customMethod = methodsForDecoration.Single(e => e.Name == "Action1"); customMethod .Invoke(decorator, CreateFakeParameters(customMethod)) .ReturnsForAnyArgs( e => { return $"Test {e[0]}"; }); methodsForDecoration.Remove(customMethod); // Пробрасываем остальные методы интерфейса без изменения. foreach (var elem in methodsForDecoration) { elem .Invoke(decorator, CreateFakeParameters(elem)) .ReturnsForAnyArgs( e => { var parameters = e.Args(); var result = elem.Invoke(source, parameters); // для передачи out параметров. for (var i = 0; i < parameters.Length; i++) { e[i] = parameters[i]; } return result; }); } // invoke test // var p = CreateFakeParameters(customMethod); // p[0] = "name"; // var r = customMethod.Invoke(decorator, p); return decorator; } | |