You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
6
6
7
-
## Package References
7
+
## Table of Content
8
8
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)
- 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:
12
38
13
39
```xml
14
40
<ProjectSdk="Microsoft.NET.Sdk">
@@ -25,47 +51,60 @@ Typical test project (excerpt):
25
51
</Project>
26
52
```
27
53
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
29
55
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:
31
57
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.
33
62
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.
38
64
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)
40
66
67
+
`Atc.Test` relies on xUnit v3 extensibility APIs:
41
68
42
-
## Test Attributes
69
+
- Async data attribute signature: `ValueTask<IReadOnlyCollection<ITheoryDataRow>> GetData(...)`.
-`DisposalTracker` parameter passed to data attributes.
43
72
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.
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.
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.
86
123
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
89
128
90
129
### Frozen Reuse Scenarios
91
130
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.
93
132
94
133
| Scenario | Attribute | Behavior |
95
134
|----------|-----------|----------|
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). |
99
138
100
-
#### 1. Positional Reuse
139
+
#### Example: Positional Reuse
101
140
102
141
```csharp
103
142
[Theory]
104
143
[InlineAutoNSubstituteData(42)]
105
144
publicvoidPositional_Frozen_Reuses_Inline_Value(
106
-
[Frozen] intnumber,// inline supplies index 0 -> frozen
107
-
SomeConsumerconsumer)// receives the same number if it depends on it
newobject?[] { newDualImpl() } // supplies IFoo parameter only
143
-
};
181
+
yieldreturnnewobject?[] { newDualImpl() };// supplies IFoo parameter only
182
+
}
144
183
145
184
[Theory]
146
185
[MemberAutoNSubstituteData(nameof(DualRow))]
147
186
publicvoidDifferent_Interface_Not_Promoted(
148
-
IFoofoo,// supplied DualImpl
149
-
[Frozen] IBarbar,// exact-type mismatch (IBar vs IFoo) -> NOT reused
150
-
UsesBarconsumer)
187
+
IFoofoo,
188
+
[Frozen] IBarbar,
189
+
UsesBarconsumer)
151
190
{
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
154
193
}
155
194
```
156
195
157
196
Design Rationale:
158
197
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 prefix—promotion reduces duplication while staying explicit.
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`. |
178
225
179
-
See [`CancellationTokenGenerator`](src/Atc.Test/Customizations/Generators/CancellationTokenGenerator.cs) for an example on how to do this.
0 commit comments