From 6599f870e67194d1d4cbfb2ccb2099f4a81ca7a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:42:53 +0000 Subject: [PATCH 1/2] Initial plan From 4c7f330633d35b67acf0b2292e729999091e0d72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 Aug 2025 22:51:52 +0000 Subject: [PATCH 2/2] Add NSubstituteDefaultFactory configuration documentation to auto-and-recursive-mocks section Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> --- docs/help/auto-and-recursive-mocks/index.md | 114 +++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/docs/help/auto-and-recursive-mocks/index.md b/docs/help/auto-and-recursive-mocks/index.md index 945016a2..3246b58f 100644 --- a/docs/help/auto-and-recursive-mocks/index.md +++ b/docs/help/auto-and-recursive-mocks/index.md @@ -103,4 +103,116 @@ var identity = Substitute.For(); Assert.That(identity.Name, Is.EqualTo(String.Empty)); Assert.That(identity.Roles().Length, Is.EqualTo(0)); -``` \ No newline at end of file +``` + +## Customizing auto values + +NSubstitute's auto value generation can be customized to suit specific needs. This is particularly useful when you want to override the default behavior for certain types or add support for types that aren't handled by default. + +The customization follows the open/closed principle: the system is open for extension but closed for modification. To customize auto values, you can: + +1. Create a derived container from `NSubstituteDefaultFactory.DefaultContainer.Customize()` +2. Use the `.Decorate()` method to enhance existing implementations +3. Replace `SubstitutionContext.Current` with your customized context, ideally using Module Initializers + +### Example: Custom Task auto values + +Here's a complete example showing how to customize auto value generation for `Task` types: + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using NSubstitute; +using NSubstitute.Core; +using NSubstitute.Core.DependencyInjection; +using NSubstitute.Routing.AutoValues; + +namespace YourProject; + +internal static class ModuleInitializer +{ + [ModuleInitializer] + internal static void ConfigureNSubstitute() + { + var customizedContainer = NSubstituteDefaultFactory.DefaultContainer.Customize(); + customizedContainer.Decorate( + (factory, _resolver) => new CustomAutoValueProvidersFactory(factory)); + + SubstitutionContext.Current = customizedContainer.Resolve(); + } +} + +internal class CustomAutoValueProvidersFactory : IAutoValueProvidersFactory +{ + private readonly IAutoValueProvidersFactory _original; + + public CustomAutoValueProvidersFactory(IAutoValueProvidersFactory original) + { + _original = original; + } + + public IReadOnlyCollection CreateProviders(ISubstituteFactory substituteFactory) + { + var originalProviders = _original.CreateProviders(substituteFactory); + + var customTaskProvider = new CustomTaskProvider(originalProviders); + + return new[] { customTaskProvider }.Concat(originalProviders).ToArray(); + } + + private class CustomTaskProvider : IAutoValueProvider + { + private readonly IReadOnlyCollection _allProviders; + + public CustomTaskProvider(IReadOnlyCollection allProviders) + { + _allProviders = allProviders; + } + + public bool CanProvideValueFor(Type type) => type == typeof(Task); + + public object GetValue(Type type) + { + // You can use _allProviders to recursively resolve inner values if needed + return Task.FromException(new InvalidOperationException("Custom failed task")); + } + } +} +``` + +### Usage + +Once configured, your customization will automatically apply to all substitutes: + +```csharp +public interface IService +{ + Task DoWorkAsync(); +} + +[Test] +public async Task CustomTaskBehavior() +{ + // arrange + var service = Substitute.For(); + + // act + var task = service.DoWorkAsync(); // Returns custom failed task + + // assert + var ex = await Assert.ThrowsAsync(async () => await task); + Assert.That(ex.Message, Is.EqualTo("Custom failed task")); +} +``` + +### Key concepts + +- **Module Initializers**: Use the `[ModuleInitializer]` attribute to configure NSubstitute before any substitutes are created. This ensures your customizations are applied globally. +- **Container Customization**: Call `DefaultContainer.Customize()` to create a copy of the default container that you can modify. +- **Decoration Pattern**: Use `.Decorate()` to wrap existing implementations with your custom logic, maintaining the original behavior while adding new functionality. +- **Immutability**: The container follows an immutable pattern - modifications create new instances rather than changing existing ones. + +This approach allows you to extend NSubstitute's behavior without modifying its core functionality, making it suitable for scenarios like Unity's `UniTask`, custom async patterns, or domain-specific value generation. \ No newline at end of file