Commit e3f8573
authored
feat(mocks): discriminate generic-method mocks by type argument (#6153)
* feat(mocks): discriminate generic-method mocks by type argument
TUnit.Mocks recorded only (memberId, arg-matchers) as the match key, so a
generic method's type arguments were dropped at the runtime boundary. Setups
that differ only by type argument (e.g. Greet<Class1>() vs Greet<Class2>(),
discussion #4981) collided and the last one always won.
Thread an optional Type[]? typeArguments (closed typeof(T) at the call site)
through setup registration, call dispatch, and verification:
- MethodSetup/CallRecord carry the type arguments; MockEngine gains object[]+Type[]
HandleCall/HandleCallWithReturn overloads and a TypeArgumentsMatch gate in
FindMatchingSetup. CallVerificationBuilder filters recorded calls by type argument.
- The source generator emits `new Type[] { typeof(T), ... }` for generic methods,
routing them through the fallback dispatch (typed dispatch can't carry type args)
while preserving any auto-mock factory.
- Add AnyType/AnyValueType wildcard markers (TUnit.Mocks.Arguments) so a setup can
match any type argument. Base/interface-constrained type params support exact
matching only; partial/wrap virtual methods record no type args and are not
discriminated (documented graceful degradation).
Non-generic methods carry null and behave exactly as before.
Note: FindMatchingSetup keeps a no-default (int, object?[]) overload distinct from
the type-arg overload so it still wins resolution over generic FindMatchingSetup<T1>.
Adds regression + wildcard + verification tests; updates generic-method snapshots.
* test(mocks): add multi-type-parameter generic-method tests
Cover discrimination by the full ordered type-argument list (T1,T2 order
sensitivity), partial wildcards (one AnyType + one concrete), exact-wins-over-
partial-wildcard, and multi-type-param verification including wildcard counts.
* refactor(mocks): dedup type-args literal + collapse redundant ctors
- Reuse MockImplBuilder.TypeArgumentsArrayLiteral from MockMembersBuilder instead
of re-building the 'new Type[] { typeof(T), ... }' string inline.
- Collapse MockMethodCall (2->1) and VoidMockMethodCall (4->2) constructors using
optional parameters; overload resolution and call sites unchanged.
No behavior or generated-output change (snapshots unchanged).
* perf(mocks): cache generic type-arg arrays + show type args in verify failures
Review feedback on PR #6153:
- Cache a generic method's type-argument array per closed instantiation
(TypeArguments.Of<T>.Value) for the common 1-2 type-param cases, so dispatch no
longer allocates a new Type[] on every generic call. Higher arities still emit a
per-call literal. (Codacy perf finding #1.)
- Include type arguments in MockVerificationException's expected-call text, so a
failed Greet<Class1>() verification reads 'Greet<Class1>(...)' not 'Greet(...)'.
Adds a regression test. (Review finding #5.)
* perf+dx(mocks): extend type-arg cache to arity 3-4, friendlier verify names, AnyValueType test
Follow-up to PR #6153 review:
- Extend TypeArguments.Of<> cache to 3 and 4 type parameters (was 1-2), so 3-4
type-param generic methods also avoid per-call Type[] allocation. Doc the
shared arrays as read-only / never-mutate.
- Strip the CLR arity suffix in verification failure messages (List, not List`1).
- Add tests proving AnyValueType works as a wildcard for 'where T : struct'
parameters (not dead code) and that struct-constrained methods discriminate
by exact type argument.
* fix(mocks): keep generic type-arg support fully backwards compatible
Earlier commits in this PR introduced two breaking changes to pre-existing public
types. Reverted while keeping the feature:
- IMockEngineAccess: removed the added CreateVerification overload (a source break
for any external implementer of this public interface). Generic type-argument
verification now routes through a new internal ITypeArgumentVerificationFactory,
implemented only by MockEngine and reached via an internal cast. Non-generic
verification stays on the unchanged public surface.
- MockMethodCall / VoidMockMethodCall: restored the original constructor signatures
that the simplify pass had collapsed into optional-parameter ctors (a binary
break), adding the type-argument overloads alongside instead of replacing them.
The public API delta for the whole PR is now additions-only: new constructor
overloads plus the new public types AnyType, AnyValueType and TypeArguments. No
generated-code or snapshot changes. 1013 integration, 62 snapshot and 30 analyzer
tests pass; runtime builds on all TFMs including netstandard2.0.
* docs(mocks): consolidate repeated read-only note in TypeArguments
Fold the identical per-field 'shared, read-only' comment on the four Of<>.Value
fields into the class-level doc. Comment-only.
* fix(mocks): degrade gracefully instead of casting when engine lacks type-arg verification
A custom IMockEngineAccess implementation passed to a generic MockMethodCall/
VoidMockMethodCall previously hit a hard InvalidCastException at verification
time. Replace the cast with a type test that falls back to the public,
non-filtering verification surface. A default interface method on
IMockEngineAccess is not viable while the library targets netstandard2.0.
* fix(mocks): thread type args through ordered verification + immutable type-arg arrays
Round 7 review fixes:
- OrderedVerification now records and matches a generic expectation's type
arguments, so VerifyInOrder discriminates Greet<Class1>() from
Greet<Class2>() like unordered verification does. Failure messages include
the type arguments. Regression tests added.
- Type-argument arrays are now ImmutableArray<Type> end-to-end
(TypeArguments.Of<T>.Value, MethodSetup, CallRecord, engine dispatch,
verification), closing the mutable-shared-array corruption risk while
keeping array-speed indexed access on the hot matching path. The arity-5+
generator fallback emits ImmutableArray.Create. All affected signatures
were introduced on this branch, so no released surface changes.
- CreateVerification routing deduped into MockCallVerification.Create,
shared by MockMethodCall and VoidMockMethodCall.
- Documented why the typed FindMatchingSetup<T1..T8> family omits a
TypeArgumentsMatch check: typed dispatch never carries call-side type
arguments, so the check would be a constant true (non-generic setups have
none; virtual/partial generic methods use the documented degradation path).
* docs(mocks): note AnyValueType is exact-match-only under additional struct constraints1 parent 3f7920a commit e3f8573
21 files changed
Lines changed: 710 additions & 107 deletions
File tree
- TUnit.Mocks.SourceGenerator.Tests/Snapshots
- TUnit.Mocks.SourceGenerator/Builders
- TUnit.Mocks.Tests
- TUnit.Mocks
- Arguments
- Setup
- Verification
Lines changed: 12 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | | - | |
| 98 | + | |
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| |||
238 | 238 | | |
239 | 239 | | |
240 | 240 | | |
241 | | - | |
| 241 | + | |
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
| |||
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
259 | | - | |
| 259 | + | |
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
| |||
347 | 347 | | |
348 | 348 | | |
349 | 349 | | |
350 | | - | |
| 350 | + | |
351 | 351 | | |
352 | 352 | | |
353 | 353 | | |
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
357 | | - | |
| 357 | + | |
358 | 358 | | |
359 | 359 | | |
360 | 360 | | |
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
364 | | - | |
| 364 | + | |
365 | 365 | | |
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
369 | 369 | | |
370 | 370 | | |
371 | 371 | | |
372 | | - | |
| 372 | + | |
373 | 373 | | |
374 | 374 | | |
375 | 375 | | |
376 | 376 | | |
377 | 377 | | |
378 | | - | |
| 378 | + | |
379 | 379 | | |
380 | 380 | | |
381 | 381 | | |
382 | 382 | | |
383 | 383 | | |
384 | 384 | | |
385 | | - | |
| 385 | + | |
386 | 386 | | |
387 | 387 | | |
388 | 388 | | |
389 | 389 | | |
390 | 390 | | |
391 | 391 | | |
392 | | - | |
| 392 | + | |
393 | 393 | | |
394 | 394 | | |
395 | 395 | | |
396 | 396 | | |
397 | 397 | | |
398 | 398 | | |
399 | 399 | | |
400 | | - | |
| 400 | + | |
401 | 401 | | |
402 | 402 | | |
403 | 403 | | |
| |||
Lines changed: 13 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
| 60 | + | |
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
| 65 | + | |
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
70 | | - | |
| 70 | + | |
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| |||
117 | 117 | | |
118 | 118 | | |
119 | 119 | | |
120 | | - | |
| 120 | + | |
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
132 | 132 | | |
133 | | - | |
| 133 | + | |
134 | 134 | | |
135 | 135 | | |
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
139 | | - | |
| 139 | + | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
145 | | - | |
| 145 | + | |
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
151 | | - | |
| 151 | + | |
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | | - | |
| 157 | + | |
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
| |||
0 commit comments