diff --git a/wiki/Architecture-Schema.md b/wiki/Architecture-Schema.md deleted file mode 100644 index 711d3a1..0000000 --- a/wiki/Architecture-Schema.md +++ /dev/null @@ -1,42 +0,0 @@ -# Architecture Schema - -The package is organized using Hexagonal Architecture (Ports and Adapters), grouped by business capability. - -## Capabilities - -- Customization: constructor orchestration, parameter-to-property mapping, and configured-value precedence. -- Value generation: plugin and value-service pipeline. -- Specimen generation: collection/type-shape builders. - -## Schema types and dependency rules - -- Domain: business values and policies. - - Can use: Domain only. -- Application: orchestration and contracts used by the public API. - - Can use: Domain and ports. -- Adapters: default implementations for contracts. - - Can use: Application and Domain ports. -- Public API: entry points exposed to consumers. - - Can use: Application abstractions and composition only. - -## Public versus internal contracts - -Public extension ports: - -- IValueCreationPlugin -- ISpecimenBuilderStrategy -- IValueCreationService -- IConstructorSelector -- IParameterPropertyMatcher -- IPropertyExpressionParser -- IPropertyValueStore - -These contracts are consumed through protected `Use...` hooks on ConstructorCustomization. - -## Value precedence rule - -Configured values are resolved in this order: - -1. Override values (With/Without) -2. Default values (SetDefault) -3. Generated values (value service pipeline) diff --git a/wiki/Creation-Pipeline.md b/wiki/Creation-Pipeline.md deleted file mode 100644 index 08793be..0000000 --- a/wiki/Creation-Pipeline.md +++ /dev/null @@ -1,59 +0,0 @@ -# Creation Pipeline: ConstructorCustomization to Final Value - -This page explains the full lifecycle from a new `ConstructorCustomization` instance to the final constructor argument values that are used to create `T`. - -## At a glance - -The package resolves each constructor argument with this priority: - -1. Test override store (`With`, `Without`) -2. Default store (`SetDefault`) -3. Generated value (`IValueCreationService` pipeline) - -Within generated values, the `IValueCreationService` pipeline is: - -1. Plugins (`IValueCreationPlugin`) -2. Specimen strategies (`ISpecimenBuilderStrategy`) -3. AutoFixture fallback (`fixture.Create(...)`) - -## Test Customization flowchart -Called from a test method directly on the `ConstructorCustomization` instance. -```csharp -var customization = new ConstructorCustomization() - .With(x => x.Property, value) - .Without(x => x.Property); -``` - -![Test-Customization flowchart](./imgs/test_customization_overrides.png) - -## Customize method flowchart -Called when the `ConstructorCustomization.Customize(IFixture)` method is called, either directly or via `fixture.Customize(...)`. - -```csharp -var fixture = new Fixture(); -var customization = new ConstructorCustomization(); -customization.Customize(fixture); -``` -![Customize method flowchart](./imgs/fixture_customization.png) - -## Create instance flowchart -Called when `ConstructorCustomization` creates an instance of `T` via the `CreateInstance` method. - -```csharp -var fixture = new Fixture(); -var customization = new ConstructorCustomization(); -customization.Customize(fixture); -var instance = fixture.Create(); // <-- calls CreateInstance -``` - -![Create instance flowchart](./imgs/create_instance.png) - -## Default Value Creation flowchart -Used when the default `ValueCreationService` is used to create a value for a constructor argument. -```csharp -var fixture = new Fixture(); -var customization = new ConstructorCustomization(); -customization.Customize(fixture); -var value = fixture.Create(); // <-- calls ValueCreationService pipeline -``` -![Default Value Creation flowchart](./imgs/create_value_pipeline.png) \ No newline at end of file diff --git a/wiki/Customizing-Behavior.md b/wiki/Customizing-Behavior.md deleted file mode 100644 index c977413..0000000 --- a/wiki/Customizing-Behavior.md +++ /dev/null @@ -1,162 +0,0 @@ -# Customizing Behavior - -This page shows the main customization tools: subclass defaults, per-test overrides, plugins, -value factories, and custom strategies. - -## Subclass defaults with SetDefault - -Override `CreateInstance` to set stable defaults for every test. Call `SetDefault` before -`base.CreateInstance(fixture)`. - -```csharp -public class OrderCustomization : ConstructorCustomization -{ - protected override Order CreateInstance(IFixture fixture) - { - SetDefault(x => x.Status, OrderStatus.Pending); - SetDefault(x => x.Amount, 100m); - return base.CreateInstance(fixture); - } -} -``` - -Any value not covered by `SetDefault` is auto-generated by AutoFixture. - -### Fixture-aware factory in SetDefault - -```csharp -SetDefault(x => x.CreatedAt, f => f.Create()); -``` - -## Per-test overrides with With and Without - -`With` and `Without` always win over subclass defaults. Use them in tests. - -```csharp -fixture.Customize(new OrderCustomization() - .With(x => x.Status, OrderStatus.Cancelled) - .Without(x => x.Amount)); // sets Amount to null -``` - -`With` and `Without` return the concrete customization type, so chaining works without casting: - -```csharp -OrderCustomization customization = new OrderCustomization() - .With(x => x.Status, OrderStatus.Shipped) - .With(x => x.Amount, 500m); -``` - -## Plugins and value factories — Configure() - -Override `Configure` to register type-specific value factories. This method is called once -when `fixture.Customize(...)` is invoked, so registrations apply to every created object. - -```csharp -public class InvoiceCustomization : ConstructorCustomization -{ - protected override void Configure() - { - // Exact type, from fixture - UseValueFor(fixture => Currency.USD); - - // Exact type, with recursive creation - UseValueFor((fixture, svc) => - new Money((decimal)svc.CreateValue(fixture, typeof(decimal))!, "USD")); - - // Predicate-based, for a whole type family - UsePlugin( - type => type.IsEnum, - (type, fixture, svc) => Enum.GetValues(type).GetValue(0)!); - } - - protected override Invoice CreateInstance(IFixture fixture) - { - SetDefault(x => x.IsPaid, false); - return base.CreateInstance(fixture); - } -} -``` - -## Custom specimen strategies — Configure() - -Implement `ISpecimenBuilderStrategy` for collection-shaped types or custom wrappers, then -register with `UseStrategy` inside `Configure`. Registered strategies run before built-in ones. - -```csharp -public class InvoiceCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseStrategy(new ImmutableArrayStrategy()); - } -} -``` - -## Configure collection size - -```csharp -var options = new ConstructorCustomizationOptions { CollectionItemCount = 5 }; - -fixture.Customize(new OrderCustomization(options)); -``` - -## Case-insensitive matching - -By default, property and parameter matching is case-insensitive. `With(x => x.FirstName, "Ada")` -matches a constructor parameter named `firstName` automatically. - -## Explicit parameter-to-property mapping - -When a constructor parameter name does not map cleanly to a property name, register an explicit -mapping in `Configure`: - -```csharp -public class PersonCustomization : ConstructorCustomization -{ - protected override void Configure() - { - MatchParameterToProperty("given_name", x => x.FirstName); - } -} -``` - -`With`, `Without`, and `SetDefault` values are still configured by property expression as usual. -Only parameter-to-property lookup changes for explicitly mapped parameters. - -Resolution order for a mapped parameter is: -1. `With` or `Without` override for mapped property -2. `SetDefault` value for mapped property -3. Auto-generated value - -Unmapped parameters still use the configured `IParameterPropertyMatcher`. - -## Advanced service extensions — Configure() - -All extension interfaces are set up in `Configure` using the corresponding `Use*` method. -Each is opt-in; the default implementation is used when the method is not called. - -| Method | Replaces default | -| -------------------------------------- | ------------------------------ | -| `UseConstructorSelector(selector)` | `LargestConstructorSelector` | -| `UseParameterPropertyMatcher(matcher)` | Case-insensitive matcher | -| `UsePropertyExpressionParser(parser)` | Member-expression parser | -| `UsePropertyValueStore(() => store)` | In-memory `PropertyValueStore` | -| `UseValueCreationService(service)` | Three-stage pipeline | - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseConstructorSelector(new SmallestConstructorSelector()); - UseParameterPropertyMatcher(new SnakeCaseMatcher()); - UsePropertyValueStore(() => new LoggingPropertyValueStore()); - } -} -``` - -> **Note:** `UseValueCreationService` replaces the entire pipeline. Plugins and strategies -> registered via `UsePlugin`, `UseValueFor`, and `UseStrategy` in the same `Configure` call -> are not automatically applied when a custom service is provided. -> -> You can access configured Plugins and UserStrategies using the protected properties on `ConstructorCustomization` and apply them manually when implementing your service. diff --git a/wiki/Extension-IConstructorSelector.md b/wiki/Extension-IConstructorSelector.md deleted file mode 100644 index 0ab4834..0000000 --- a/wiki/Extension-IConstructorSelector.md +++ /dev/null @@ -1,49 +0,0 @@ -# Extension: IConstructorSelector - -Use this extension when your type has multiple constructors and you want full control over -which one is used. - -## When to use it - -- Prefer parameterless constructors for specific tests. -- Prefer constructors marked with custom attributes. -- Avoid very large constructors for expensive object graphs. - -## Minimal implementation - -```csharp -using System.Reflection; -using ConstructorCustomization.AutoFixture.Customization.Application.Ports; - -public sealed class SmallestConstructorSelector : IConstructorSelector -{ - public ConstructorInfo SelectConstructor(Type targetType, ConstructorInfo[] constructors) - { - if (constructors.Length == 0) - { - throw new InvalidOperationException($"No public constructors for {targetType.FullName}"); - } - - return constructors.OrderBy(c => c.GetParameters().Length).First(); - } -} -``` - -## Wire it in - -Call `UseConstructorSelector` inside your `Configure` override: - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseConstructorSelector(new SmallestConstructorSelector()); - } -} -``` - -## Default behavior - -The built-in selector chooses the constructor with the largest parameter count. - diff --git a/wiki/Extension-IParameterPropertyMatcher.md b/wiki/Extension-IParameterPropertyMatcher.md deleted file mode 100644 index d2b4f56..0000000 --- a/wiki/Extension-IParameterPropertyMatcher.md +++ /dev/null @@ -1,63 +0,0 @@ -# Extension: IParameterPropertyMatcher - -Use this extension when your constructor parameter names do not map directly to property names. -This is an advanced extension point; the built-in case-insensitive matcher handles most cases. - -## Typical case - -Constructor uses `first_name`, property is `FirstName`. - -## Minimal implementation - -```csharp -using System.Reflection; -using ConstructorCustomization.AutoFixture.Customization.Application.Ports; - -public sealed class SnakeCaseMatcher : IParameterPropertyMatcher -{ - public bool TryGetPropertyName( - ParameterInfo parameter, - IEnumerable configuredPropertyNames, - out string propertyName) - { - var normalized = parameter.Name?.Replace("_", "") ?? string.Empty; - - var match = configuredPropertyNames.FirstOrDefault(x => - string.Equals(x, normalized, StringComparison.OrdinalIgnoreCase)); - - if (match is null) - { - propertyName = string.Empty; - return false; - } - - propertyName = match; - return true; - } -} -``` - -## Wire it in - -Call `UseParameterPropertyMatcher` inside your `Configure` override: - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseParameterPropertyMatcher(new SnakeCaseMatcher()); - } -} -``` - -## Interaction with explicit mappings - -If you register `MatchParameterToProperty("param", x => x.Property)` in `Configure`, that mapping -is used first for that parameter. Your custom matcher still handles all parameters that are not -explicitly mapped. - -## Default behavior - -The built-in matcher compares names case-insensitively and also checks PascalCase variants. - diff --git a/wiki/Extension-IPropertyExpressionParser.md b/wiki/Extension-IPropertyExpressionParser.md deleted file mode 100644 index 50c84c7..0000000 --- a/wiki/Extension-IPropertyExpressionParser.md +++ /dev/null @@ -1,49 +0,0 @@ -# Extension: IPropertyExpressionParser - -Use this extension when you want special rules for parsing `With(...)` and `Without(...)` expressions. -This is an advanced extension point; the built-in parser handles all normal member expressions. - -## When to use it - -- Support custom member naming conventions. -- Normalize aliases. -- Enforce project-specific expression rules. - -## Minimal implementation - -```csharp -using System.Linq.Expressions; -using ConstructorCustomization.AutoFixture.Customization.Application.Ports; - -public sealed class LowerCasePropertyParser : IPropertyExpressionParser -{ - public string GetPropertyName(LambdaExpression propertyExpression) - { - if (propertyExpression.Body is not MemberExpression m) - { - throw new ArgumentException("Expected member expression", nameof(propertyExpression)); - } - - return m.Member.Name.ToLowerInvariant(); - } -} -``` - -## Tip - -If you customize parsing, your matcher and value store should use compatible naming logic. - -## Wire it in - -Call `UsePropertyExpressionParser` inside your `Configure` override: - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UsePropertyExpressionParser(new LowerCasePropertyParser()); - } -} -``` - diff --git a/wiki/Extension-IPropertyValueStore.md b/wiki/Extension-IPropertyValueStore.md deleted file mode 100644 index 9343d1d..0000000 --- a/wiki/Extension-IPropertyValueStore.md +++ /dev/null @@ -1,63 +0,0 @@ -# Extension: IPropertyValueStore - -Use this extension when you need custom storage behavior for configured overrides. -This is an advanced extension point; the built-in in-memory store handles all normal cases. - -## When to use it - -- Add auditing or diagnostics. -- Use custom key normalization. -- Persist configured values outside memory. - -## Minimal implementation - -```csharp -using ConstructorCustomization.AutoFixture.Customization.Application.Ports; - -public sealed class LoggingPropertyValueStore : IPropertyValueStore -{ - private readonly Dictionary values = - new(StringComparer.OrdinalIgnoreCase); - - public IEnumerable PropertyNames => values.Keys; - - public bool Contains(string propertyName) => values.ContainsKey(propertyName); - - public bool TryGetValue(string propertyName, out object? value) - => values.TryGetValue(propertyName, out value); - - public void SetValue(string propertyName, object? value) - { - Console.WriteLine($"Set {propertyName}"); - values[propertyName] = value; - } - - public bool RemoveValue(string propertyName) => values.Remove(propertyName); - - public void Clear() => values.Clear(); -} -``` - -## Tip - -Use the same comparer strategy as your matcher and parser to avoid mismatches. - -## Wire it in - -Call `UsePropertyValueStore` inside your `Configure` override. Pass a factory — it is called -twice per `Customize()` call, once for the test-override store (`With`/`Without`) and once for -the subclass-default store (`SetDefault`): - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UsePropertyValueStore(() => new LoggingPropertyValueStore()); - } -} -``` - -Existing test overrides set before `Customize()` is called are migrated to the new store -automatically. - diff --git a/wiki/Extension-ISpecimenBuilderStrategy.md b/wiki/Extension-ISpecimenBuilderStrategy.md deleted file mode 100644 index 05a0f61..0000000 --- a/wiki/Extension-ISpecimenBuilderStrategy.md +++ /dev/null @@ -1,61 +0,0 @@ -# Extension: ISpecimenBuilderStrategy - -Use this extension for type-specific generation rules (for example immutable collections or -custom wrappers). Register custom strategies inside the `Configure` override of your -customization class using `UseStrategy`. - -## When to use it - -- Generate known collection shapes. -- Build special value objects. -- Override generation for one specific type family. - -## Minimal implementation - -```csharp -using AutoFixture; -using ConstructorCustomization.AutoFixture; -using ConstructorCustomization.AutoFixture.SpecimenGeneration.Ports; -using ConstructorCustomization.AutoFixture.ValueGeneration.Ports; - -public sealed class ImmutableArrayStrategy : ISpecimenBuilderStrategy -{ - public bool CanBuild(Type type) - => type.IsGenericType && - type.GetGenericTypeDefinition().FullName == "System.Collections.Immutable.ImmutableArray`1"; - - public object? Build( - Type type, - IFixture fixture, - IValueCreationService valueCreationService, - ConstructorCustomizationOptions options) - { - var elementType = type.GetGenericArguments()[0]; - var array = Array.CreateInstance(elementType, options.CollectionItemCount); - - for (var i = 0; i < options.CollectionItemCount; i++) - { - array.SetValue(valueCreationService.CreateValue(fixture, elementType), i); - } - - var createRange = type.GetMethod("CreateRange", new[] { typeof(IEnumerable<>).MakeGenericType(elementType) }); - return createRange?.Invoke(null, new object?[] { array.Cast() }); - } -} -``` - -## Register with UseStrategy - -Call `UseStrategy` inside your `Configure` override. Custom strategies are evaluated before -built-in ones. - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseStrategy(new ImmutableArrayStrategy()); - } -} -``` - diff --git a/wiki/Extension-IValueCreationPlugin.md b/wiki/Extension-IValueCreationPlugin.md deleted file mode 100644 index 4ffcb54..0000000 --- a/wiki/Extension-IValueCreationPlugin.md +++ /dev/null @@ -1,108 +0,0 @@ -# Extension: IValueCreationPlugin - -Plugins control how specific types are created for constructor arguments that have no explicit -`With` override. Register plugins inside the `Configure` override of your customization class. - -## Value creation pipeline - -For every constructor argument with no test override, the value creation pipeline evaluates in -this order: - -1. **Plugins** — registered in order via `UseValueFor(...)` or `UsePlugin(...)` in `Configure` -2. **Built-in strategies** — handles arrays, lists, dictionaries, and sets -3. **AutoFixture fallback** — `fixture.Create()` for everything else - -## Registration inside Configure() - -### Exact type, fixture access - -```csharp -public class OrderCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseValueFor(fixture => "deterministic-string"); - UseValueFor(fixture => new DateTime(2020, 1, 1)); - } -} -``` - -### Exact type, with recursive creation - -Use `IValueCreationService` when the created value itself needs recursively generated parts. - -```csharp -public class InvoiceCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseValueFor((fixture, svc) => - { - var amount = (decimal)svc.CreateValue(fixture, typeof(decimal))!; - return new Money(amount, "USD"); - }); - } -} -``` - -### Predicate-based, for type families - -```csharp -public class ReportCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UsePlugin( - type => type.IsEnum, - (type, fixture, svc) => Enum.GetValues(type).GetValue(0)!); - } -} -``` - -## Implement IValueCreationPlugin directly - -For stateful or reusable logic, implement `IValueCreationPlugin` and register with `UsePlugin`: - -```csharp -using ConstructorCustomization.AutoFixture.ValueGeneration.Ports; - -public sealed class CurrencyPlugin : IValueCreationPlugin -{ - public bool CanCreate(Type type) => type == typeof(Currency); - - public object? Create(Type type, IFixture fixture, IValueCreationService valueCreationService) - => Currency.USD; -} - -public class InvoiceCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UsePlugin(new CurrencyPlugin()); - } -} -``` - -## Precedence - -When multiple plugins are registered, the **first one whose `CanCreate` returns `true`** wins. -Register more specific plugins before less specific ones. - -```csharp -public class OrderCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseValueFor(fixture => "specific"); // checked first - UsePlugin( - type => type == typeof(string), - (type, fixture, svc) => "never reached"); // never invoked for string - } -} -``` - -## Default behavior is preserved - -For any type without a matching plugin, the package falls through to its built-in strategies -(array, list, dictionary, set) and ultimately to AutoFixture. You never lose any existing -default behavior by registering plugins. diff --git a/wiki/Extension-IValueCreationService.md b/wiki/Extension-IValueCreationService.md deleted file mode 100644 index fd7238f..0000000 --- a/wiki/Extension-IValueCreationService.md +++ /dev/null @@ -1,57 +0,0 @@ -# Extension: IValueCreationService - -> **For most use cases, use plugins instead.** -> Override `Configure` in your customization class and call `UseValueFor(...)` or -> `UsePlugin(...)`. See [Extension: IValueCreationPlugin](Extension-IValueCreationPlugin). - -Replace `IValueCreationService` only when you need to change the entire value creation pipeline, -not just individual types. This is a rarely needed, advanced extension point. - -## When to use it - -- Completely replace the three-stage pipeline (plugins → strategies → fixture fallback). -- Apply cross-cutting logic before any type matching. -- Integrate an external randomization or determinism framework at the root level. - -## Minimal implementation - -```csharp -using System.Reflection; -using AutoFixture; -using ConstructorCustomization.AutoFixture.ValueGeneration.Ports; - -public sealed class DeterministicValueCreationService : IValueCreationService -{ - public object? CreateValue(IFixture fixture, ParameterInfo parameter) - => CreateValue(fixture, parameter.ParameterType); - - public object? CreateValue(IFixture fixture, Type type) - { - if (type == typeof(string)) return "fixed-value"; - if (type == typeof(DateTime)) return new DateTime(2000, 1, 1); - - return fixture.Create(type, new AutoFixture.Kernel.SpecimenContext(fixture)); - } -} -``` - -## Wire it in - -Call `UseValueCreationService` inside your `Configure` override: - -```csharp -public class MyCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseValueCreationService(new DeterministicValueCreationService()); - } -} -``` - -> **Note:** When a custom service is registered, any plugins and strategies also registered in -> the same `Configure` call via `UsePlugin`, `UseValueFor`, or `UseStrategy` are **not** -> automatically applied. The custom service takes full ownership of the pipeline. -> -> You can access configured Plugins and UserStrategies using the protected properties on `ConstructorCustomization` and apply them manually when implementing your service. - diff --git a/wiki/Extensions-Overview.md b/wiki/Extensions-Overview.md deleted file mode 100644 index d1af21c..0000000 --- a/wiki/Extensions-Overview.md +++ /dev/null @@ -1,109 +0,0 @@ -# Extensions Overview - -The library is built around a subclass-first pattern and a set of small, optional extension -points. Most real-world needs are covered by the first three tools below. Reach for deeper -extension points only when a concrete test need appears. - -## Primary extension points - -### 1. Subclass defaults — `SetDefault` in `CreateInstance` - -Override `CreateInstance` to declare stable defaults for your type. Tests can override any -of these with `With` / `Without`. - -```csharp -public class OrderCustomization : ConstructorCustomization -{ - protected override Order CreateInstance(IFixture fixture) - { - SetDefault(x => x.Status, OrderStatus.Pending); - return base.CreateInstance(fixture); - } -} -``` - -### 2. Type-specific value factories — `UseValueFor` in `Configure` - -Control how specific argument types are created. Runs before built-in collection strategies -and before the AutoFixture fallback. - -```csharp -protected override void Configure() -{ - UseValueFor(fixture => Currency.USD); - UseValueFor((fixture, svc) => - new Money((decimal)svc.CreateValue(fixture, typeof(decimal))!, "USD")); -} -``` - -### 3. Predicate-based plugins — `UsePlugin` in `Configure` - -Handle whole type families (all enums, all records, etc.) - -```csharp -protected override void Configure() -{ - UsePlugin( - type => type.IsEnum, - (type, fixture, svc) => Enum.GetValues(type).GetValue(0)!); -} -``` - -Or register a reusable `IValueCreationPlugin` implementation: - -```csharp -public sealed class CurrencyPlugin : IValueCreationPlugin -{ - public bool CanCreate(Type type) => type == typeof(Currency); - public object? Create(Type type, IFixture fixture, IValueCreationService svc) - => Currency.USD; -} - -protected override void Configure() -{ - UsePlugin(new CurrencyPlugin()); -} -``` - -## Secondary extension points - -| Extension | Where to hook in | When to use | -| --------------------------- | ------------------------------------------------- | ------------------------------------------------- | -| `ISpecimenBuilderStrategy` | `UseStrategy(...)` in `Configure` | Custom collection shapes or value-object wrappers | -| `IValueCreationService` | `UseValueCreationService(...)` in `Configure` | Replace the entire three-stage pipeline | -| `IConstructorSelector` | `UseConstructorSelector(...)` in `Configure` | Non-default constructor selection logic | -| `IParameterPropertyMatcher` | `UseParameterPropertyMatcher(...)` in `Configure` | Snake_case or other non-standard naming | -| `IPropertyExpressionParser` | `UsePropertyExpressionParser(...)` in `Configure` | Custom expression/member naming rules | -| `IPropertyValueStore` | `UsePropertyValueStore(...)` in `Configure` | Auditing, diagnostics, custom key normalization | - -## Full example - -```csharp -public class InvoiceCustomization : ConstructorCustomization -{ - protected override void Configure() - { - UseValueFor(fixture => Currency.EUR); - UsePlugin(type => type.IsEnum, (type, f, svc) => Enum.GetValues(type).GetValue(0)!); - UseStrategy(new ImmutableArrayStrategy()); - } - - protected override Invoice CreateInstance(IFixture fixture) - { - SetDefault(x => x.IsPaid, false); - SetDefault(x => x.IssuedAt, f => f.Create()); - return base.CreateInstance(fixture); - } -} - -// In a test: -fixture.Customize(new InvoiceCustomization() - .With(x => x.IsPaid, true)); -``` - -## Read next - -- [Extension: IValueCreationPlugin](Extension-IValueCreationPlugin) -- [Extension: ISpecimenBuilderStrategy](Extension-ISpecimenBuilderStrategy) -- [Extension: IValueCreationService](Extension-IValueCreationService) - diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md deleted file mode 100644 index 62491c7..0000000 --- a/wiki/Getting-Started.md +++ /dev/null @@ -1,182 +0,0 @@ -# Getting Started - -This guide shows the primary usage pattern for ConstructorCustomization.AutoFixture. - -## Install - -```powershell -dotnet add package ConstructorCustomization.AutoFixture -``` - -## Primary pattern — create a typed customization class - -The recommended approach is to create a typed subclass of `ConstructorCustomization` -for each type you want to customize. This gives you a single place to define how a type is -created across all tests. - -### 1. Define a customization class - -```csharp -using AutoFixture; -using ConstructorCustomization.AutoFixture; - -public class Person -{ - public Person(string firstName, string lastName, int age) - { - FirstName = firstName; - LastName = lastName; - Age = age; - } - - public string FirstName { get; } - public string LastName { get; } - public int Age { get; } -} - -public class PersonCustomization : ConstructorCustomization -{ - protected override Person CreateInstance(IFixture fixture) - { - // Set stable defaults for every test. Tests can override these with With/Without. - SetDefault(x => x.FirstName, "Ada"); - SetDefault(x => x.LastName, "Lovelace"); - SetDefault(x => x.Age, 36); - return base.CreateInstance(fixture); - } -} -``` - -### 2. Register the customization - -```csharp -var fixture = new Fixture(); -fixture.Customize(new PersonCustomization()); - -var person = fixture.Create(); -// person.FirstName == "Ada", person.LastName == "Lovelace", person.Age == 36 -``` - -### 3. Override specific values per test - -Use `With` and `Without` when a single test needs a different value. These always win over -the subclass defaults set with `SetDefault`. - -```csharp -var person = fixture.Create(); // uses PersonCustomization defaults - -fixture.Customize(new PersonCustomization() - .With(x => x.Age, 18)); // only Age changes; FirstName and LastName use defaults - -var youngPerson = fixture.Create(); -// youngPerson.Age == 18 -``` - -## Explicit parameter-to-property mapping - -When a constructor parameter name does not match a property name, register an explicit mapping -inside `Configure`. You still configure values normally through `With`, `Without`, or `SetDefault` -using the property expression. - -```csharp -public class User -{ - public User(string given_name, string family_name) - { - FirstName = given_name; - LastName = family_name; - } - - public string FirstName { get; } - public string LastName { get; } -} - -public class UserCustomization : ConstructorCustomization -{ - protected override void Configure() - { - MatchParameterToProperty("given_name", x => x.FirstName); - MatchParameterToProperty("family_name", x => x.LastName); - } - - protected override User CreateInstance(IFixture fixture) - { - SetDefault(x => x.FirstName, "Ada"); - SetDefault(x => x.LastName, "Lovelace"); - return base.CreateInstance(fixture); - } -} -``` - -Parameters without an explicit mapping continue to use normal parameter-to-property matching. - -## Out-of-the-box usage (no subclass) - -When you need a quick customization without creating a class, use `ConstructorCustomization` directly. -This uses AutoFixture defaults for any argument not explicitly configured. - -```csharp -fixture.Customize(new ConstructorCustomization() - .With(x => x.FirstName, "Ada") - .With(x => x.LastName, "Lovelace")); -``` - -## Deferred values - -Use a factory delegate when the value should be evaluated at object creation time. - -```csharp -public class PersonCustomization : ConstructorCustomization -{ - protected override Person CreateInstance(IFixture fixture) - { - SetDefault(x => x.FirstName, () => Guid.NewGuid().ToString("N")); - SetDefault(x => x.Age, f => f.Create()); - return base.CreateInstance(fixture); - } -} -``` - -The same factory forms are available for `With`: - -```csharp -fixture.Customize(new PersonCustomization() - .With(x => x.FirstName, () => $"Test-{Guid.NewGuid():N}") - .With(x => x.Age, f => f.Create())); -``` - -## Null override - -Call `Without` to set a property to `null`: - -```csharp -fixture.Customize(new PersonCustomization() - .Without(x => x.LastName)); -``` - -## Clear all test overrides - -Call `Clear()` to remove all values set via `With` and `Without`, restoring subclass defaults: - -```csharp -var customization = new PersonCustomization() - .With(x => x.Age, 99); - -customization.Clear(); // age will use the SetDefault value again -``` - -## How it works - -1. `Configure()` is called once when `fixture.Customize(...)` is invoked. Register plugins and strategies there. -2. For each `fixture.Create()` call, `CreateInstance(fixture)` runs, which calls `SetDefault` to populate defaults. -3. For each constructor parameter, the lookup order is: - - Test override (`With` / `Without`) — wins unconditionally - - Subclass default (`SetDefault`) - - AutoFixture auto-generation - -## Next steps - -- [Customizing Behavior](Customizing-Behavior) — plugins, type-specific factories, and strategies -- [Extensions Overview](Extensions-Overview) — full extension point map - -Go to [Extensions Overview](Extensions-Overview) to customize behavior. diff --git a/wiki/Home.md b/wiki/Home.md deleted file mode 100644 index a43f452..0000000 --- a/wiki/Home.md +++ /dev/null @@ -1,28 +0,0 @@ -# ConstructorCustomization.AutoFixture Wiki - -This wiki is focused on practical usage and extension patterns. - -## Start here - -1. [Getting Started](Getting-Started) -2. [Customizing Behavior](Customizing-Behavior) -3. [Creation Pipeline](Creation-Pipeline) -4. [Extensions Overview](Extensions-Overview) - -## Extension pages - -Each page covers one extension point only. - -- [Extension: IConstructorSelector](Extension-IConstructorSelector) -- [Extension: IParameterPropertyMatcher](Extension-IParameterPropertyMatcher) -- [Extension: IPropertyExpressionParser](Extension-IPropertyExpressionParser) -- [Extension: IPropertyValueStore](Extension-IPropertyValueStore) -- [Extension: IValueCreationService](Extension-IValueCreationService) -- [Extension: ISpecimenBuilderStrategy](Extension-ISpecimenBuilderStrategy) - -## Reading order - -1. Start with `With(...)`, `Without(...)`, and `Clear()` from [Getting Started](Getting-Started). -2. Tune default behavior with [Customizing Behavior](Customizing-Behavior). -3. Implement custom extensions only when a real test need appears. - diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md deleted file mode 100644 index 9d7e0d9..0000000 --- a/wiki/_Sidebar.md +++ /dev/null @@ -1,15 +0,0 @@ -## ConstructorCustomization.AutoFixture - -- [Home](Home) -- [Getting Started](Getting-Started) -- [Creation Pipeline](Creation-Pipeline) -- [Architecture Schema](Architecture-Schema) -- [Customizing Behavior](Customizing-Behavior) -- [Extensions Overview](Extensions-Overview) - -## Extension Points - -- [IValueCreationPlugin](Extension-IValueCreationPlugin) -- [IValueCreationService](Extension-IValueCreationService) -- [ISpecimenBuilderStrategy](Extension-ISpecimenBuilderStrategy) - diff --git a/wiki/imgs/create_instance.png b/wiki/imgs/create_instance.png deleted file mode 100644 index 3a18cc8..0000000 Binary files a/wiki/imgs/create_instance.png and /dev/null differ diff --git a/wiki/imgs/create_value_pipeline.png b/wiki/imgs/create_value_pipeline.png deleted file mode 100644 index efe5b25..0000000 Binary files a/wiki/imgs/create_value_pipeline.png and /dev/null differ diff --git a/wiki/imgs/fixture_customization.png b/wiki/imgs/fixture_customization.png deleted file mode 100644 index 38f4457..0000000 Binary files a/wiki/imgs/fixture_customization.png and /dev/null differ diff --git a/wiki/imgs/test_customization_overrides.png b/wiki/imgs/test_customization_overrides.png deleted file mode 100644 index 070bc9b..0000000 Binary files a/wiki/imgs/test_customization_overrides.png and /dev/null differ