Skip to content

Commit a160e4a

Browse files
perkopsPer Kops
authored andcommitted
docs(readme): clarify xunit v3-only compatibility and unsupported scenarios
1 parent c2cee48 commit a160e4a

File tree

1 file changed

+130
-76
lines changed

1 file changed

+130
-76
lines changed

README.md

Lines changed: 130 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1-
# ATC Test
1+
# Introduction
22

33
![NuGet Version](https://img.shields.io/nuget/v/Atc.Test.svg?logo=nuget&style=for-the-badge)
44

5-
Common tools for writing tests using xUnit, AutoFixture, NSubstitute and FluentAssertions.
5+
`Atc.Test` is a .NET helper library that streamlines authoring tests with xUnit v3, AutoFixture, NSubstitute, and FluentAssertions. It provides rich data attributes, automatic specimen customization, and ergonomic frozen value reuse to reduce ceremony and improve test readability.
66

7-
## Package References
7+
## Table of Content
88

9-
Add `Atc.Test` to the project containing your tests or to a shared test utilities project.
9+
- [Introduction](#introduction)
10+
- [Table of Content](#table-of-content)
11+
- [Features](#features)
12+
- [Getting Started](#getting-started)
13+
- [Install Package](#install-package)
14+
- [Why xUnit Must Be Referenced Directly](#why-xunit-must-be-referenced-directly)
15+
- [First Test Examples](#first-test-examples)
16+
- [Advanced Usage](#advanced-usage)
17+
- [Frozen Reuse Scenarios](#frozen-reuse-scenarios)
18+
- [Auto Registration of Customizations](#auto-registration-of-customizations)
19+
- [Helper Extensions](#helper-extensions)
20+
- [Requirements](#requirements)
21+
- [How to Contribute](#how-to-contribute)
1022

11-
Typical test project (excerpt):
23+
## Features
24+
25+
- Data attributes integrating AutoFixture + NSubstitute: `AutoNSubstituteData`, `InlineAutoNSubstituteData`, `MemberAutoNSubstituteData`, `ClassAutoNSubstituteData`.
26+
- Automatic interface/abstract substitution via NSubstitute.
27+
- Exact-type frozen promotion for member data (reuse supplied instance across later `[Frozen]` parameters).
28+
- Deterministic fixture configuration with opt‑in auto-registration of custom `ICustomization` / `ISpecimenBuilder` via `[AutoRegister]`.
29+
- Convenience extensions: equivalency options, substitute inspection helpers, task timeout helpers, object protected member access.
30+
- Multi-targeted (netstandard2.1, net8.0, net9.0) for broad compatibility.
31+
- Clear separation of concerns: you own the xUnit runner/version.
32+
33+
## Getting Started
34+
35+
### Install Package
36+
37+
Add `Atc.Test` to your test project along with explicit references to xUnit and the test SDK:
1238

1339
```xml
1440
<Project Sdk="Microsoft.NET.Sdk">
@@ -25,47 +51,60 @@ Typical test project (excerpt):
2551
</Project>
2652
```
2753

28-
`Atc.Test` depends on `xunit.v3.extensibility.core` for the extensibility APIs, but it intentionally does NOT bring in the `xunit.v3` meta-package for you.
54+
### Why xUnit Must Be Referenced Directly
2955

30-
### Why you must still reference xUnit directly
56+
`Atc.Test` depends on `xunit.v3.extensibility.core` (the extensibility surface) but intentionally does **not** bring in the `xunit.v3` meta-package:
3157

32-
We do not make `xunit.v3` transitive because:
58+
- Avoid NU1701 warnings from runner assets not targeting `netstandard2.1`.
59+
- Let you pin or float the xUnit version independently.
60+
- Keep framework + runner decisions in your test project for predictable upgrades.
61+
- Preserve the library’s focus: providing attributes/utilities instead of prescribing test infrastructure.
3362

34-
- The `xunit.v3` meta-package pulls in runner-related assets that are not targeted for `netstandard2.1`; this causes NU1701 framework fallback warnings if we referenced it from the multi-targeted library.
35-
- Consumers should control the exact xUnit version (pin or float) in their test project without the library forcing an upgrade cadence.
36-
- Keeping test framework + runner packages at the application (test project) layer avoids unexpected breaking changes when updating `Atc.Test`.
37-
- Separation of concerns: `Atc.Test` provides data attributes/utilities; the test project owns the choice of framework + runner configuration.
63+
If you want a different xUnit patch/minor version, change the `<PackageReference Include="xunit.v3" ... />` line—no changes to `Atc.Test` required.
3864

39-
If you need a different xUnit patch/minor version, just adjust the `<PackageReference Include="xunit.v3" ... />` in your test project.
65+
#### xUnit v3 Only (Incompatible With v2)
4066

67+
`Atc.Test` relies on xUnit v3 extensibility APIs:
4168

42-
## Test Attributes
69+
- Async data attribute signature: `ValueTask<IReadOnlyCollection<ITheoryDataRow>> GetData(...)`.
70+
- `ITheoryDataRow` & metadata (Label, Explicit, Timeout) preservation.
71+
- `DisposalTracker` parameter passed to data attributes.
4372

44-
| Name | Description |
45-
|-|-|
46-
| `AutoNSubstituteData` | Provides auto-generated data specimens generated by AutoFixture and NSubstitute as an extension to XUnit's [Theory] attribute.|
47-
| `InlineAutoNSubstituteData` | Provides a data source for a data theory, with the data coming from inline values combined with auto-generated data specimens generated by AutoFixture and NSubstitute.|
48-
| `MemberAutoNSubstituteData` | Provides a data source for a data theory, with the data coming from one of the following sources and combined with auto-generated data specimens generated by AutoFixture and NSubstitute.|
73+
These do not exist in xUnit v2. Attempting to use a v2 framework or runner will result in discovery failures or compile errors.
4974

50-
### Usage Examples
75+
| Scenario | Outcome |
76+
|----------|---------|
77+
| Replace `xunit.v3` with `xunit` (v2) | Build errors: missing v3 types & method signatures |
78+
| Run with legacy v2 runner | Test discovery fails (no v3 discovery support) |
79+
| Mix projects: some v2, some using `Atc.Test` | Allowed, but they must not share v3-based base test classes |
80+
| Remove explicit `xunit.v3` reference | Build error / missing types (transitive reference intentionally absent) |
81+
82+
Optional guard rails (not included by default):
83+
84+
```xml
85+
<!-- Example MSBuild check you can copy into a test project -->
86+
<Target Name="ValidateXunitV3" BeforeTargets="Build">
87+
<Error Condition="!Exists('$(NuGetPackageRoot)xunit.v3/')"
88+
Text="Atc.Test requires an explicit PackageReference to xunit.v3 in the test project." />
89+
</Target>
90+
```
91+
92+
“Why no v2 support?” the answer is simply that the library embraces the cleaner v3 data extensibility model; back-porting would require a parallel code path and reduce clarity.
93+
94+
### First Test Examples
5195

5296
```csharp
5397
public class CalculatorTests
5498
{
5599
[Theory]
56100
[AutoNSubstituteData]
57101
public void AutoData_Generates_Specimens(int a, int b, Calculator sut)
58-
{
59-
var result = sut.Add(a, b);
60-
result.Should().Be(a + b);
61-
}
102+
=> sut.Add(a, b).Should().Be(a + b);
62103

63104
[Theory]
64105
[InlineAutoNSubstituteData(2, 3)]
65106
public void InlineAutoData_Mixes_Inline_And_Auto(int a, int b, Calculator sut)
66-
{
67-
sut.Add(a, b).Should().Be(5);
68-
}
107+
=> sut.Add(a, b).Should().Be(5);
69108

70109
public static IEnumerable<object?[]> MemberSource()
71110
{
@@ -76,110 +115,125 @@ public class CalculatorTests
76115
[Theory]
77116
[MemberAutoNSubstituteData(nameof(MemberSource))]
78117
public void MemberAutoData_Augments_Member_Data(int a, int b, Calculator sut)
79-
{
80-
sut.Add(a, b).Should().Be(a + b);
81-
}
118+
=> sut.Add(a, b).Should().Be(a + b);
82119
}
83120
```
84121

85-
All remaining parameters (after those satisfied by inline/member data) are populated using an AutoFixture `IFixture` customized with NSubstitute for interfaces/abstract classes.
122+
All remaining parameters (after inline/member supplied ones) are created via an AutoFixture `IFixture` that substitutes interfaces/abstract classes using NSubstitute.
86123

87-
> **Note:**
88-
> NSubstitute is used when the type being created is an interface or abstract class.
124+
> **Note**
125+
> NSubstitute is used automatically when the requested type is an interface or abstract class.
126+
127+
## Advanced Usage
89128

90129
### Frozen Reuse Scenarios
91130

92-
When you decorate a parameter with `[Frozen]`, its resolved instance is reused for any other specimens needing that type. The `MemberAutoNSubstituteData` attribute supports an additional convenience: **exact-type promotion** of an earlier supplied value to a later `[Frozen]` parameter whose slot was not part of the supplied member row.
131+
When you decorate a parameter with `[Frozen]`, its resolved instance is reused for other specimens requiring that exact type. `MemberAutoNSubstituteData` adds **exact-type promotion**: reusing an earlier supplied value for a later `[Frozen]` parameter when that later slot was not part of the member row.
93132

94133
| Scenario | Attribute | Behavior |
95134
|----------|-----------|----------|
96-
| Positional frozen reuse | `ClassAutoNSubstituteData` + `MemberAutoNSubstituteData` | If the data row supplies a value at the same parameter index as a `[Frozen]` parameter, that value is frozen and reused. |
97-
| Exact-type promotion (member data only) | `MemberAutoNSubstituteData` | If a later `[Frozen] T` parameter has no supplied value (index beyond row length), we look for an earlier supplied value whose parameter type is exactly `T` and freeze it. |
98-
| No interface/base promotion | Both | We do NOT promote across interface or base types—only exact parameter type matches. |
135+
| Positional frozen reuse | `ClassAutoNSubstituteData` & `MemberAutoNSubstituteData` | If a value is supplied at the same index as a `[Frozen]` parameter, it is frozen and reused. |
136+
| Exact-type promotion (member data only) | `MemberAutoNSubstituteData` | Later `[Frozen] T` without a supplied value reuses an earlier supplied parameter whose declared type is exactly `T`. |
137+
| No interface/base promotion | Both | Only exact parameter type matches are reused (no interface or base class widening). |
99138

100-
#### 1. Positional Reuse
139+
#### Example: Positional Reuse
101140

102141
```csharp
103142
[Theory]
104143
[InlineAutoNSubstituteData(42)]
105144
public void Positional_Frozen_Reuses_Inline_Value(
106-
[Frozen] int number, // inline supplies index 0 -> frozen
107-
SomeConsumer consumer) // receives the same number if it depends on it
145+
[Frozen] int number,
146+
SomeConsumer consumer)
108147
{
109-
consumer.NumberDependency.Should().Be(number);
148+
consumer.NumberDependency.Should().Be(number);
110149
}
111150
```
112151

113-
#### 2. Exact-Type Promotion (Member Data Only)
152+
#### Example: Exact-Type Promotion (Member Data)
114153

115154
```csharp
116-
public static IEnumerable<object?[]> ServiceRow() => new []
155+
public static IEnumerable<object?[]> ServiceRow()
117156
{
118-
new object?[] { Substitute.For<IMyService>() } // supplies parameter 0 only
119-
};
157+
yield return new object?[] { Substitute.For<IMyService>() }; // supplies parameter 0 only
158+
}
120159

121160
[Theory]
122161
[MemberAutoNSubstituteData(nameof(ServiceRow))]
123162
public void Promotion_Reuses_Earlier_Same_Type(
124-
IMyService supplied, // index 0 supplied
125-
[Frozen] IMyService frozenLater, // not supplied -> promoted reuse
126-
NeedsService consumer) // receives frozen instance
163+
IMyService supplied,
164+
[Frozen] IMyService frozenLater,
165+
NeedsService consumer)
127166
{
128-
frozenLater.Should().BeSameAs(supplied);
129-
consumer.Service.Should().BeSameAs(supplied);
167+
frozenLater.Should().BeSameAs(supplied);
168+
consumer.Service.Should().BeSameAs(supplied);
130169
}
131170
```
132171

133-
#### 3. Non-Promotion Across Different Interfaces
172+
#### Example: Non-Promotion Across Different Interfaces
134173

135174
```csharp
136175
public interface IFoo {}
137176
public interface IBar {}
138177
public class DualImpl : IFoo, IBar {}
139178

140-
public static IEnumerable<object?[]> DualRow() => new []
179+
public static IEnumerable<object?[]> DualRow()
141180
{
142-
new object?[] { new DualImpl() } // supplies IFoo parameter only
143-
};
181+
yield return new object?[] { new DualImpl() }; // supplies IFoo parameter only
182+
}
144183

145184
[Theory]
146185
[MemberAutoNSubstituteData(nameof(DualRow))]
147186
public void Different_Interface_Not_Promoted(
148-
IFoo foo, // supplied DualImpl
149-
[Frozen] IBar bar, // exact-type mismatch (IBar vs IFoo) -> NOT reused
150-
UsesBar consumer)
187+
IFoo foo,
188+
[Frozen] IBar bar,
189+
UsesBar consumer)
151190
{
152-
bar.Should().NotBeSameAs(foo); // separate instance
153-
consumer.Bar.Should().BeSameAs(bar); // consumer wired to frozen IBar
191+
bar.Should().NotBeSameAs(foo); // separate instance
192+
consumer.Bar.Should().BeSameAs(bar); // consumer wired to frozen IBar
154193
}
155194
```
156195

157196
Design Rationale:
158197

159-
- Class data is typically authored with full positional intent—implicit promotion could hide mistakes.
160-
- Member data commonly supplies only a prefix, so exact-type promotion avoids boilerplate duplication while staying predictable.
161-
- Restricting to exact type (no interface/base assignability) prevents accidental cross-interface freezes (e.g., a dual-implemented object hijacking a different abstraction).
198+
- Class data is usually fully positional—implicit promotion might hide mistakes.
199+
- Member data often supplies only a prefixpromotion reduces duplication while staying explicit.
200+
- Exact-type restriction avoids cross-interface bleed (e.g., dual implementations hijacking unrelated abstractions).
162201

163-
## Test Helpers
202+
### Auto Registration of Customizations
164203

165-
| Name | Description |
166-
|-|-|
167-
| `EquivalencyAssertionOptionsExtensions` | Extensions for FluentAssertions to compare dates with a precision when using `.BeEquivalentTo()`.|
168-
| `FixtureFactory` | Static factory for creating AutoFixture `Fixture` instances.|
169-
| `ObjectExtensions` | Extensions calling protected members on an object.|
170-
| `SubstituteExtensions` | Extensions for NSubstitutes to wait for calls and get arguments of a received call.|
171-
| `TaskExtensions` | Extensions for Tasks to add timeouts when awaiting. |
204+
Any `ICustomization` or `ISpecimenBuilder` decorated with `[AutoRegister]` is added automatically to the fixture created by `FixtureFactory.Create()`.
172205

173-
## Extensibility
206+
Example:
174207

175-
The default `Fixture` returned by the `FixtureFactory.Create()` method is used for all the attributes mentioned above.
208+
```csharp
209+
[AutoRegister]
210+
public class GuidCustomization : ICustomization
211+
{
212+
public void Customize(IFixture fixture) => fixture.Register(() => Guid.NewGuid());
213+
}
214+
```
215+
216+
### Helper Extensions
176217

177-
To add customizations to this, you can add the `AutoRegisterAttribute` to any custom `ICustomization` or `ISpecimenBuilder` to have it automatically added to the Fixture.
218+
| Helper | Purpose |
219+
|--------|---------|
220+
| `EquivalencyAssertionOptionsExtensions` | Adds convenience config (e.g., date precision) to FluentAssertions equivalency. |
221+
| `SubstituteExtensions` | Inspect substitutes, wait for calls, retrieve arguments. |
222+
| `TaskExtensions` | Await with timeouts. |
223+
| `ObjectExtensions` | Access protected members via reflection helpers. |
224+
| `FixtureFactory` | Central factory returning a consistently customized `IFixture`. |
178225

179-
See [`CancellationTokenGenerator`](src/Atc.Test/Customizations/Generators/CancellationTokenGenerator.cs) for an example on how to do this.
226+
## Requirements
180227

181-
## How to contribute
228+
| Aspect | Value |
229+
|--------|-------|
230+
| Target Frameworks | netstandard2.1, net8.0, net9.0 |
231+
| Test Framework | xUnit v3 (must be referenced directly) |
232+
| Mocking | NSubstitute (transitively used for interfaces/abstract classes) |
233+
| Assertions | FluentAssertions (recommended) |
182234

183-
[Contribution Guidelines](https://atc-net.github.io/introduction/about-atc#how-to-contribute)
235+
## How to Contribute
184236

237+
[Contribution Guidelines](https://atc-net.github.io/introduction/about-atc#how-to-contribute)
185238
[Coding Guidelines](https://atc-net.github.io/introduction/about-atc#coding-guidelines)
239+

0 commit comments

Comments
 (0)