diff --git a/.editorconfig b/.editorconfig
index 5a911ad..9be2882 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -447,22 +447,22 @@ dotnet_naming_rule.parameters_rule.severity = warning
# Meziantou
# https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm
-dotnet_diagnostic.MA0003.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0003.md
-dotnet_diagnostic.MA0004.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0004.md
-dotnet_diagnostic.MA0006.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0006.md
+dotnet_diagnostic.MA0003.severity = suggestion # Add argument name to improve readability
+dotnet_diagnostic.MA0004.severity = suggestion # Use Task.ConfigureAwait(false)
+dotnet_diagnostic.MA0006.severity = none # Use String.Equals instead of equality operator
dotnet_diagnostic.MA0011.severity = none # Duplicate of CA1305
-dotnet_diagnostic.MA0016.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0016.md
-dotnet_diagnostic.MA0025.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0025.md
-dotnet_diagnostic.MA0026.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0026.md
-dotnet_diagnostic.MA0028.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0028.md
+dotnet_diagnostic.MA0016.severity = error # Prefer return collection abstraction over implementation
+dotnet_diagnostic.MA0025.severity = suggestion # Implement functionality instead of throwing NotImplementedException
+dotnet_diagnostic.MA0026.severity = suggestion # Fix TODO comment
+dotnet_diagnostic.MA0028.severity = none # Optimize StringBuilder usage
dotnet_diagnostic.MA0038.severity = none # Duplicate of CA1822
-dotnet_diagnostic.MA0048.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0048.md
+dotnet_diagnostic.MA0048.severity = error # File name must match type name
# Microsoft - Code Analysis
# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
-dotnet_diagnostic.CA1014.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1014.md
-dotnet_diagnostic.CA1068.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1068.md
+dotnet_diagnostic.CA1014.severity = none # Mark assemblies with CLSCompliantAttribute
+dotnet_diagnostic.CA1068.severity = error # CancellationToken parameters must come last
dotnet_diagnostic.CA1305.severity = error
dotnet_diagnostic.CA1308.severity = suggestion # Normalize strings to uppercase
dotnet_diagnostic.CA1510.severity = suggestion # Use ArgumentNullException throw helper
@@ -471,7 +471,7 @@ dotnet_diagnostic.CA1512.severity = suggestion # Use ArgumentOutOfRangeExce
dotnet_diagnostic.CA1513.severity = suggestion # Use ObjectDisposedException throw helper
dotnet_diagnostic.CA1514.severity = error # Avoid redundant length argument
dotnet_diagnostic.CA1515.severity = suggestion # Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
-dotnet_diagnostic.CA1707.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1707.md
+dotnet_diagnostic.CA1707.severity = error # Identifiers should not contain underscores
dotnet_diagnostic.CA1812.severity = none
dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1849.severity = error # Call async methods when in an async method
@@ -494,7 +494,7 @@ dotnet_diagnostic.CA1869.severity = suggestion # Cache and reuse 'JsonSeria
dotnet_diagnostic.CA1870.severity = suggestion # Use a cached 'SearchValues' instance
dotnet_diagnostic.CA1871.severity = suggestion # Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull'
dotnet_diagnostic.CA1872.severity = suggestion # Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'
-dotnet_diagnostic.CA2007.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA2007.md
+dotnet_diagnostic.CA2007.severity = suggestion # Do not directly await a Task
dotnet_diagnostic.CA2017.severity = error # Parameter count mismatch
dotnet_diagnostic.CA2018.severity = error # The count argument to Buffer.BlockCopy should specify the number of bytes to copy
dotnet_diagnostic.CA2019.severity = error # ThreadStatic fields should not use inline initialization
@@ -510,12 +510,12 @@ dotnet_diagnostic.CA2261.severity = error # Do not use ConfigureAwaitO
dotnet_diagnostic.CA2262.severity = suggestion # Set 'MaxResponseHeadersLength' properly
dotnet_diagnostic.CA2263.severity = suggestion # Prefer generic overload when type is known
dotnet_diagnostic.CA2264.severity = error # Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull'
-dotnet_diagnostic.IDE0005.severity = warning # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/IDE0005.md
+dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary using directives
dotnet_diagnostic.IDE0010.severity = suggestion # Populate switch
dotnet_diagnostic.IDE0028.severity = suggestion # Collection initialization can be simplified
dotnet_diagnostic.IDE0021.severity = suggestion # Use expression body for constructor
dotnet_diagnostic.IDE0055.severity = none # Fix formatting
-dotnet_diagnostic.IDE0058.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/IDE0058.md
+dotnet_diagnostic.IDE0058.severity = none # Remove unnecessary expression value
dotnet_diagnostic.IDE0061.severity = suggestion # Use expression body for local function
dotnet_diagnostic.IDE0130.severity = suggestion # Namespace does not match folder structure
dotnet_diagnostic.IDE0290.severity = none # Use primary constructor
@@ -525,7 +525,7 @@ dotnet_diagnostic.IDE0305.severity = suggestion # Collection initialization
# Microsoft - Compiler Errors
# https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/
-dotnet_diagnostic.CS4014.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCompilerErrors/CS4014.md
+dotnet_diagnostic.CS4014.severity = error # Call is not awaited
# SecurityCodeScan
@@ -534,29 +534,29 @@ dotnet_diagnostic.CS4014.severity = error # https://github.com/atc-net/
# StyleCop
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers
-dotnet_diagnostic.SA1009.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1009.md
+dotnet_diagnostic.SA1009.severity = none # Closing parenthesis should be spaced correctly
dotnet_diagnostic.SA1010.severity = none # False positive when using collection initializers
-dotnet_diagnostic.SA1101.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1101.md
-dotnet_diagnostic.SA1122.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1122.md
-dotnet_diagnostic.SA1133.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1133.md
-dotnet_diagnostic.SA1200.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1200.md
-dotnet_diagnostic.SA1201.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1201.md
-dotnet_diagnostic.SA1202.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1202.md
-dotnet_diagnostic.SA1204.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1204.md
-dotnet_diagnostic.SA1413.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1413.md
-dotnet_diagnostic.SA1600.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1600.md
+dotnet_diagnostic.SA1101.severity = none # Prefix local calls with this
+dotnet_diagnostic.SA1122.severity = error # Use string.Empty for empty strings
+dotnet_diagnostic.SA1133.severity = error # Do not combine attributes
+dotnet_diagnostic.SA1200.severity = none # Using directives should be placed correctly
+dotnet_diagnostic.SA1201.severity = none # Elements should appear in the correct order
+dotnet_diagnostic.SA1202.severity = none # Elements should be ordered by access
+dotnet_diagnostic.SA1204.severity = none # Static elements should appear before instance elements
+dotnet_diagnostic.SA1413.severity = error # Use trailing commas in multi-line initializers
+dotnet_diagnostic.SA1600.severity = none # Elements should be documented
dotnet_diagnostic.SA1601.severity = none
-dotnet_diagnostic.SA1602.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1602.md
-dotnet_diagnostic.SA1604.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1604.md
-dotnet_diagnostic.SA1623.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1623.md
-dotnet_diagnostic.SA1629.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1629.md
-dotnet_diagnostic.SA1633.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1633.md
-dotnet_diagnostic.SA1649.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1649.md
+dotnet_diagnostic.SA1602.severity = none # Enumeration items should be documented
+dotnet_diagnostic.SA1604.severity = none # Element documentation should have summary
+dotnet_diagnostic.SA1623.severity = none # Property summary documentation should match accessors
+dotnet_diagnostic.SA1629.severity = none # Documentation text should end with a period
+dotnet_diagnostic.SA1633.severity = none # File should have header
+dotnet_diagnostic.SA1649.severity = error # File name should match first type name
# SonarAnalyzer.CSharp
# https://rules.sonarsource.com/csharp
-dotnet_diagnostic.S1135.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/SonarAnalyzerCSharp/S1135.md
+dotnet_diagnostic.S1135.severity = suggestion # Track uses of TODO tags
dotnet_diagnostic.S2629.severity = none # Don't use string interpolation in logging message templates.
dotnet_diagnostic.S3358.severity = none # Extract this nested ternary operation into an independent statement.
dotnet_diagnostic.S6602.severity = none # "Find" method should be used instead of the "FirstOrDefault"
diff --git a/Directory.Build.props b/Directory.Build.props
index 6ba4c77..8e0cb86 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -50,9 +50,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 1cb4f2a..94fd17b 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,99 @@
-# Introduction
+# Atc.Test

`Atc.Test` is a .NET helper library that streamlines authoring tests with xUnit v3, AutoFixture, NSubstitute, and AwesomeAssertions. It provides rich data attributes, automatic specimen customization, and ergonomic frozen value reuse to reduce ceremony and improve test readability.
-## Why Atc.Test
+## Atc.Test in 30 Seconds
-> You can “just wire everything manually” with plain xUnit and hand‑rolled mocks—so why use this instead?
+Every dependency your test does not care about is noise. `Atc.Test` lets you declare only the parameters the test is actually about — everything else is generated, substituted and wired for you.
-| Problem Without | What You Gain With `Atc.Test` | Why It Matters Over Time |
-|-----------------|-------------------------------|--------------------------|
-| Repeating constructor/mocker boilerplate in every test | Parameter-only intent: you list just what the test cares about | Lower cognitive load; faster review – noise removed |
-| Fragile refactors (add a ctor param ⇒ touch many files) | Fixture-driven auto‑supply of new dependencies | Constructor churn becomes O(1) instead of O(N tests) |
-| Divergent ad‑hoc mock styles (naming, setup order) | Central factory + consistent frozen reuse semantics | Suite stays uniform; easier large-scale edits / audits |
-| Accidental duplicate substitutes for logically single collaborator | `[Frozen]` exact-type reuse + early supplied promotion (member data) | Prevents subtle mismatch bugs & expectation gaps |
-| Manual re-creation of “shared conventions” (recursion handling, generators) | One-time customization via `[AutoRegister]` | New test inherits standards automatically |
-| AI-generated setup drifts over time | Declarative attributes act as a stable policy layer | Reduces maintenance & future prompt dependency |
+**Without `Atc.Test`**
-### When It Delivers the Most Value
+```csharp
+[Fact]
+public void Should_Return_Order_When_Found()
+{
+ // Arrange
+ var repository = Substitute.For();
+ var pricing = Substitute.For();
+ var audit = Substitute.For();
+ var clock = Substitute.For();
+ var sut = new OrderService(repository, pricing, audit, clock);
+
+ var orderId = Guid.NewGuid();
+ var order = new Order { Id = orderId, Name = "Test order" };
+ repository.Get(orderId).Returns(order);
+
+ // Act
+ var result = sut.GetOrder(orderId);
+
+ // Assert
+ result.Should().BeSameAs(order);
+}
+```
-* Mid/large test suites (hundreds+ of theory cases).
-* Domain services with evolving constructor graphs / dependencies.
-* Teams that value refactor safety and consistent test style.
-* Situations where only a few parameters per test truly matter.
+**With `Atc.Test`**
-### When Bare xUnit (+ manual mocks) May Be Enough
+```csharp
+[Theory]
+[AutoNSubstituteData]
+public void Should_Return_Order_When_Found(
+ [Frozen] IOrderRepository repository,
+ Guid orderId,
+ Order order,
+ OrderService sut)
+{
+ // Arrange
+ repository.Get(orderId).Returns(order);
-* Very small or short‑lived codebases.
-* Highly bespoke object graphs where you override almost every generated value anyway.
-* Educational contexts emphasizing explicit wiring for learning.
+ // Act
+ var result = sut.GetOrder(orderId);
-### Summary
+ // Assert
+ result.Should().BeSameAs(order);
+}
+```
-`Atc.Test` trades a tiny amount of initial abstraction for compounding savings in refactors, readability, and consistency. AI can quickly generate boilerplate; this library’s value is eliminating the need for that boilerplate in the first place—and giving you a single, policy‑driven locus for customization and reuse.
+`IPricingService`, `IAuditLog` and `TimeProvider` are still supplied to the constructor — they are simply no longer in your way. Add a fifth constructor parameter tomorrow and this test does not change.
## Table of Content
-* [Introduction](#introduction)
- * [Why Atc.Test](#why-atctest)
-* [Table of Content](#table-of-content)
- * [Features](#features)
- * [Getting Started](#getting-started)
- * [Install Package](#install-package)
- * [Why xUnit Must Be Referenced Directly](#why-xunit-must-be-referenced-directly)
- * [First Test Examples](#first-test-examples)
- * [Advanced Usage](#advanced-usage)
- * [Frozen Reuse Scenarios](#frozen-reuse-scenarios)
- * [Auto Registration of Customizations](#auto-registration-of-customizations)
- * [Helper Extensions](#helper-extensions)
- * [Requirements](#requirements)
- * [How to Contribute](#how-to-contribute)
+* [Atc.Test in 30 Seconds](#atctest-in-30-seconds)
+* [Cheat Sheet](#cheat-sheet)
+* [Features](#features)
+* [Getting Started](#getting-started)
+ * [Install Package](#install-package)
+ * [Version Compatibility](#version-compatibility)
+ * [Why xUnit Must Be Referenced Directly](#why-xunit-must-be-referenced-directly)
+ * [First Test Examples](#first-test-examples)
+* [Common Recipes](#common-recipes)
+* [Built-in Specimen Support](#built-in-specimen-support)
+* [Working With Atc.Test (deep dive)](docs/working-with.md)
+* [Advanced Usage](#advanced-usage)
+ * [Frozen Reuse Scenarios](#frozen-reuse-scenarios)
+ * [Auto Registration of Customizations](#auto-registration-of-customizations)
+ * [Helper Extensions](#helper-extensions)
+* [Troubleshooting / FAQ](#troubleshooting--faq)
+* [Why Atc.Test](#why-atctest)
+* [Requirements](#requirements)
+* [Migrating from FluentAssertions](#migrating-from-fluentassertions)
+* [How to Contribute](#how-to-contribute)
+
+## Cheat Sheet
+
+Most users only ever need this table.
+
+| What you want | Use | Notes |
+|---------------|-----|-------|
+| Generate every parameter automatically | `[Theory] [AutoNSubstituteData]` | Interfaces/abstract types become NSubstitute substitutes. |
+| Mix fixed values with generated ones | `[Theory] [InlineAutoNSubstituteData(2, 3)]` | Inline values fill the leading parameters. |
+| Drive a theory from a member | `[Theory] [MemberAutoNSubstituteData(nameof(Source))]` | Supports **exact-type promotion** for `[Frozen]`. |
+| Drive a theory from a class | `[Theory] [ClassAutoNSubstituteData(typeof(Source))]` | Positional frozen injection only, no promotion. |
+| Reuse one instance across the graph | `[Frozen] IMyService service` | The same instance is injected into everything needing exactly `IMyService`. |
+| Teach the fixture about your own type | `[AutoRegister]` on an `ICustomization` / `ISpecimenBuilder` | Discovered automatically, no registration call needed. |
+| Build a fixture by hand | `FixtureFactory.Create()` | Same configuration the attributes use. |
+| Fail a hanging async test fast | `await task.AddTimeout()` | Defaults to 5s, and is **bypassed when a debugger is attached**. |
## Features
@@ -57,6 +101,7 @@
* Automatic interface/abstract substitution via NSubstitute.
* Exact-type frozen promotion for member data (reuse supplied instance across later `[Frozen]` parameters).
* Deterministic fixture configuration with opt‑in auto-registration of custom `ICustomization` / `ISpecimenBuilder` via `[AutoRegister]`.
+* Built-in specimen support for types AutoFixture handles poorly or not at all — `CancellationToken`, `DateOnly`, `TimeOnly`, `Uri`, `TimeProvider`, immutable collections and recursive graphs.
* Convenience extensions: equivalency options, substitute inspection helpers, task timeout helpers, object protected member access.
* Clear separation of concerns: you own the xUnit runner/version.
@@ -75,12 +120,20 @@ Add `Atc.Test` to your test project along with explicit references to xUnit and
-
+
```
+### Version Compatibility
+
+| `Atc.Test` | xUnit | Target framework | Assertions |
+|------------|-------|------------------|------------|
+| 3.x | xUnit v3 (`xunit.v3` 4.x) | `net10.0` | AwesomeAssertions |
+
+For releases prior to 3.0.0, see the [CHANGELOG](CHANGELOG.md).
+
### Why xUnit Must Be Referenced Directly
`Atc.Test` depends on `xunit.v3.extensibility.core` (the extensibility surface) but intentionally does **not** bring in the `xunit.v3` meta-package:
@@ -154,6 +207,139 @@ All remaining parameters (after inline/member supplied ones) are created via an
> **Note**
> NSubstitute is used automatically when the requested type is an interface or abstract class.
+## Common Recipes
+
+### Mock one dependency, auto-generate the rest
+
+Decorate the one you care about with `[Frozen]`. The same instance is injected into the system under test.
+
+```csharp
+[Theory]
+[AutoNSubstituteData]
+public void Should_Persist_Order(
+ [Frozen] IOrderRepository repository,
+ Order order,
+ OrderService sut)
+{
+ sut.Save(order);
+
+ repository.Received(1).Save(order);
+}
+```
+
+### Assert a substitute was called with a specific argument
+
+`ReceivedCallWithArgument` returns the single argument of type `T` across all received calls, so you can assert on it directly instead of writing an `Arg.Is` predicate. It fails if there is not exactly one.
+
+```csharp
+sut.Save(order);
+
+var saved = repository.ReceivedCallWithArgument();
+
+saved.Status.Should().Be(OrderStatus.Pending);
+```
+
+Use `ReceivedCallsWithArguments` when several arguments of that type are expected; it returns them all.
+
+### Wait for an asynchronous call
+
+For code that dispatches work in the background, `WaitForCall` waits until the call arrives or the timeout elapses.
+
+```csharp
+await handler.WaitForCall(x => x.Handle(Arg.Any()));
+```
+
+`WaitForCallForAnyArgs` is the argument-agnostic variant. Both default to a 5 second timeout, accept an explicit `TimeSpan`, and throw on timeout.
+
+### Stop a hanging test from blocking the suite
+
+`AddTimeout` fails a task that never completes, instead of letting the run hang until the framework kills it.
+
+```csharp
+var result = await sut.ProcessAsync().AddTimeout();
+
+await sut.StartAsync().AddTimeout(TimeSpan.FromSeconds(30));
+```
+
+The default is 5 seconds. When a debugger is attached the timeout is **ignored**, so stepping through a test does not trip a `TimeoutException`.
+
+To await several tasks and collect their results, `AwaitTasks` reads more naturally than `Task.WhenAll`:
+
+```csharp
+var results = await new[] { first, second, third }.AwaitTasks();
+```
+
+### Freeze the clock
+
+`TimeProvider` resolves to a provider reporting a fixed instant, so time-dependent code is deterministic and the test can assert against the same value.
+
+```csharp
+[Theory]
+[AutoNSubstituteData]
+public void Should_Stamp_Creation_Time(
+ [Frozen] TimeProvider timeProvider,
+ Order order,
+ OrderService sut)
+{
+ sut.Create(order);
+
+ order.CreatedUtc.Should().Be(timeProvider.GetUtcNow());
+}
+```
+
+### Control generation for your own type
+
+Any `ICustomization` or `ISpecimenBuilder` marked with `[AutoRegister]` is picked up automatically — no registration call anywhere.
+
+```csharp
+[AutoRegister]
+public class PositiveAmountCustomization : ICustomization
+{
+ public void Customize(IFixture fixture)
+ => fixture.Customize(c => c.FromFactory(() => new Amount(100)));
+}
+```
+
+### Compare objects containing timestamps
+
+Round-tripped timestamps rarely match to the tick. `CompareDateTimeUsingCloseTo` relaxes the comparison.
+
+```csharp
+actual.Should().BeEquivalentTo(
+ expected,
+ o => o.CompareDateTimeUsingCloseTo());
+```
+
+The default precision is 1000 ms. Pass an `int` for a different millisecond precision, or a `TimeSpan` when that reads better:
+
+```csharp
+o => o.CompareDateTimeUsingCloseTo(precision: 500)
+o => o.CompareDateTimeUsingCloseTo(TimeSpan.FromSeconds(2))
+```
+
+### Compare strings ignoring formatting
+
+```csharp
+actualJson.Should().HaveSimilarJsonAs(expectedJson);
+actualXml.Should().HaveSimilarXmlAs(expectedXml);
+actualText.Should().HaveSimilarContentAs(expectedText);
+```
+
+## Built-in Specimen Support
+
+`FixtureFactory.Create()` — and therefore every data attribute — handles the following out of the box. Everything else falls back to standard AutoFixture behaviour.
+
+| Type | Behaviour |
+|------|-----------|
+| `CancellationToken` | A token that has **not** been canceled. |
+| `DateOnly` | Derived from a generated `DateTime`. |
+| `TimeOnly` | Derived from a generated `DateTime`. |
+| `Uri` | A readable absolute URI on the reserved `example.org` domain. |
+| `TimeProvider` | A provider reporting a fixed, generated UTC instant (`LocalTimeZone` is UTC). Combine with `[Frozen]` to share the instant. |
+| `ImmutableArray`, `ImmutableList`, `ImmutableHashSet`, `ImmutableSortedSet`, `ImmutableDictionary`, `ImmutableSortedDictionary` | Populated by generating a mutable counterpart and converting it. |
+| Interfaces / abstract classes | Substituted with NSubstitute. |
+| Recursive types | Recursion is omitted rather than throwing — the recursive member is left `null`. |
+
## Advanced Usage
### Frozen Reuse Scenarios
@@ -243,6 +429,8 @@ public class GuidCustomization : ICustomization
}
```
+The decorated type must expose a parameterless constructor. Discovery scans the assemblies loaded into the current `AppDomain`, so customizations declared in your test project are found without any configuration.
+
### Helper Extensions
| Helper | Purpose |
@@ -251,8 +439,62 @@ public class GuidCustomization : ICustomization
| `SubstituteExtensions` | Inspect substitutes, wait for calls, retrieve arguments. |
| `TaskExtensions` | Await with timeouts. |
| `ObjectExtensions` | Access protected members via reflection helpers. |
+| `StringExtensions` | Compare text, XML and JSON disregarding formatting. |
| `FixtureFactory` | Central factory returning a consistently customized `IFixture`. |
+## Troubleshooting / FAQ
+
+**`NotSupportedException` when a fixture is created**
+A type marked `[AutoRegister]` implements neither `ICustomization` nor `ISpecimenBuilder`. Implement one of them, or remove the attribute.
+
+**My `[AutoRegister]` type is never applied**
+It must expose a parameterless constructor, and its assembly must be loaded. Types in your test project are loaded automatically; types in a separate assembly that nothing references may not be.
+
+**`[Frozen]` did not reuse the instance from my member data**
+Promotion matches the **exact** declared parameter type. A value supplied as `DualImpl` will not be promoted into a `[Frozen] IFoo` parameter. Declare the supplying parameter with the same type as the frozen one.
+
+**Class data does not promote frozen values, but member data does**
+This is intentional. Class data is normally fully positional, where implicit promotion would hide mistakes; member data commonly supplies only a leading subset. Use `MemberAutoNSubstituteData` when you want promotion.
+
+**A property on my generated object is unexpectedly `null`**
+The type is recursive. `Atc.Test` replaces AutoFixture's throwing recursion behaviour with `OmitOnRecursionBehavior`, so the recursive member is omitted instead of failing the test.
+
+**Build errors about missing xUnit types**
+`Atc.Test` does not bring in the `xunit.v3` meta-package by design. Add an explicit `` to your test project.
+
+**My timestamps differ by a few ticks in `BeEquivalentTo`**
+Use `o => o.CompareDateTimeUsingCloseTo()`, or freeze `TimeProvider` so both sides derive from the same instant.
+
+## Why Atc.Test
+
+> You can “just wire everything manually” with plain xUnit and hand‑rolled mocks—so why use this instead?
+
+| Problem Without | What You Gain With `Atc.Test` | Why It Matters Over Time |
+|-----------------|-------------------------------|--------------------------|
+| Repeating constructor/mocker boilerplate in every test | Parameter-only intent: you list just what the test cares about | Lower cognitive load; faster review – noise removed |
+| Fragile refactors (add a ctor param ⇒ touch many files) | Fixture-driven auto‑supply of new dependencies | Constructor churn becomes O(1) instead of O(N tests) |
+| Divergent ad‑hoc mock styles (naming, setup order) | Central factory + consistent frozen reuse semantics | Suite stays uniform; easier large-scale edits / audits |
+| Accidental duplicate substitutes for logically single collaborator | `[Frozen]` exact-type reuse + early supplied promotion (member data) | Prevents subtle mismatch bugs & expectation gaps |
+| Manual re-creation of “shared conventions” (recursion handling, generators) | One-time customization via `[AutoRegister]` | New test inherits standards automatically |
+| AI-generated setup drifts over time | Declarative attributes act as a stable policy layer | Reduces maintenance & future prompt dependency |
+
+### When It Delivers the Most Value
+
+* Mid/large test suites (hundreds+ of theory cases).
+* Domain services with evolving constructor graphs / dependencies.
+* Teams that value refactor safety and consistent test style.
+* Situations where only a few parameters per test truly matter.
+
+### When Bare xUnit (+ manual mocks) May Be Enough
+
+* Very small or short‑lived codebases.
+* Highly bespoke object graphs where you override almost every generated value anyway.
+* Educational contexts emphasizing explicit wiring for learning.
+
+### Summary
+
+`Atc.Test` trades a tiny amount of initial abstraction for compounding savings in refactors, readability, and consistency. AI can quickly generate boilerplate; this library’s value is eliminating the need for that boilerplate in the first place—and giving you a single, policy‑driven locus for customization and reuse.
+
## Requirements
| Aspect | Value |
diff --git a/docs/working-with.md b/docs/working-with.md
new file mode 100644
index 0000000..036c88d
--- /dev/null
+++ b/docs/working-with.md
@@ -0,0 +1,395 @@
+# Working With Atc.Test
+
+A long-form guide to using `Atc.Test` in a real test suite. The [README](../README.md) covers
+installation and the fastest path to a first passing test; this document goes deeper into the
+data attributes, `[Frozen]` semantics, customization, and the helper extensions.
+
+## Table of Content
+
+* [The Problem This Solves](#the-problem-this-solves)
+* [Data Attributes](#data-attributes)
+ * [AutoNSubstituteData](#autonsubstitutedata)
+ * [InlineAutoNSubstituteData](#inlineautonsubstitutedata)
+ * [MemberAutoNSubstituteData](#memberautonsubstitutedata)
+ * [ClassAutoNSubstituteData](#classautonsubstitutedata)
+ * [Choosing Between Them](#choosing-between-them)
+* [The Frozen Attribute](#the-frozen-attribute)
+ * [Basic Frozen Reuse](#basic-frozen-reuse)
+ * [Positional Frozen Reuse](#positional-frozen-reuse)
+ * [Exact-Type Promotion](#exact-type-promotion)
+ * [What Is Not Promoted](#what-is-not-promoted)
+* [Auto-Registration of Customizations](#auto-registration-of-customizations)
+* [The FixtureFactory](#the-fixturefactory)
+* [Helper Extensions](#helper-extensions)
+* [Where to Go Next](#where-to-go-next)
+
+## The Problem This Solves
+
+Without a library like this, tests accumulate noise. Every test method wires up its own fixture,
+freezes the right dependencies, creates substitutes, and constructs the system under test. With an
+evolving constructor graph this becomes a maintenance burden: adding a single constructor parameter
+can cascade into touching dozens or hundreds of test files.
+
+`Atc.Test` applies one principle — **you list only the parameters that matter to your test.**
+Everything else is generated, substituted, and wired up for you.
+
+The practical consequences:
+
+* Constructor churn becomes **O(1)** instead of **O(N tests)**. Add a dependency; the fixture supplies it.
+* Divergent per-developer mock styles are replaced by one factory and one set of reuse semantics.
+* Duplicate substitutes for a logically single collaborator are prevented by `[Frozen]`.
+* Shared conventions — recursion handling, custom generators — are registered once and inherited everywhere.
+
+This pays off most in mid-to-large suites, in domain services with complex constructor graphs, and on
+teams that care about refactor safety. For a very small or short-lived codebase, plain xUnit with
+hand-rolled mocks may be enough.
+
+## Data Attributes
+
+Four attributes cover the common scenarios. Picking the right one is most of the learning curve.
+
+### AutoNSubstituteData
+
+The workhorse. Every parameter is auto-generated; interfaces and abstract classes become NSubstitute
+substitutes.
+
+```csharp
+[Theory]
+[AutoNSubstituteData]
+public void GetValue_ShouldReturnDataFromService(
+ [Frozen] IMyService service,
+ MyController sut)
+{
+ service.GetValue().Returns(42);
+
+ var result = sut.Get();
+
+ result.Should().Be(42);
+}
+```
+
+`service` is a substitute for `IMyService`. Because it is `[Frozen]`, the *same* instance is injected
+into `MyController`, so the `Returns` setup is visible to the system under test. You never write
+`Substitute.For()` or `new Fixture()`.
+
+### InlineAutoNSubstituteData
+
+Supply specific values for the leading parameters and let AutoFixture generate the rest — the
+auto-mocking equivalent of xUnit's `[InlineData]`.
+
+```csharp
+[Theory]
+[InlineAutoNSubstituteData(10, 20)]
+[InlineAutoNSubstituteData(5, 5)]
+[InlineAutoNSubstituteData(0, -1)]
+public void Add_ShouldWorkWithSpecificValues(int a, int b, Calculator sut)
+ => sut.Add(a, b).Should().Be(a + b);
+```
+
+Inline values are assigned to parameters in order. Anything left over is auto-generated. Use this for
+boundary conditions and edge cases.
+
+### MemberAutoNSubstituteData
+
+For test data that cannot be expressed as compile-time constants. Works like `[MemberData]`, but
+augments the supplied rows with generated specimens.
+
+```csharp
+public static IEnumerable