Skip to content

Commit 2d8bc4e

Browse files
committed
docs(readme): add frozen reuse, promotion, and non-promotion examples
1 parent b72f3c6 commit 2d8bc4e

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,79 @@ All remaining parameters (after those satisfied by inline/member data) are popul
8787
> **Note:**
8888
> NSubstitute is used when the type being created is an interface or abstract class.
8989
90+
### Frozen Reuse Scenarios
91+
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.
93+
94+
| Scenario | Attribute | Behavior |
95+
|----------|-----------|----------|
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. |
99+
100+
#### 1. Positional Reuse
101+
102+
```csharp
103+
[Theory]
104+
[InlineAutoNSubstituteData(42)]
105+
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
108+
{
109+
consumer.NumberDependency.Should().Be(number);
110+
}
111+
```
112+
113+
#### 2. Exact-Type Promotion (Member Data Only)
114+
115+
```csharp
116+
public static IEnumerable<object?[]> ServiceRow() => new []
117+
{
118+
new object?[] { Substitute.For<IMyService>() } // supplies parameter 0 only
119+
};
120+
121+
[Theory]
122+
[MemberAutoNSubstituteData(nameof(ServiceRow))]
123+
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
127+
{
128+
frozenLater.Should().BeSameAs(supplied);
129+
consumer.Service.Should().BeSameAs(supplied);
130+
}
131+
```
132+
133+
#### 3. Non-Promotion Across Different Interfaces
134+
135+
```csharp
136+
public interface IFoo {}
137+
public interface IBar {}
138+
public class DualImpl : IFoo, IBar {}
139+
140+
public static IEnumerable<object?[]> DualRow() => new []
141+
{
142+
new object?[] { new DualImpl() } // supplies IFoo parameter only
143+
};
144+
145+
[Theory]
146+
[MemberAutoNSubstituteData(nameof(DualRow))]
147+
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)
151+
{
152+
bar.Should().NotBeSameAs(foo); // separate instance
153+
consumer.Bar.Should().BeSameAs(bar); // consumer wired to frozen IBar
154+
}
155+
```
156+
157+
Design Rationale:
158+
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).
162+
90163
## Test Helpers
91164

92165
| Name | Description |

0 commit comments

Comments
 (0)