Skip to content

fix: support mocking types from extern-aliased references - #921

Merged
vbreuss merged 2 commits into
mainfrom
fix/extern-alias-types
Oct 31, 2026
Merged

vbreuss merged 2 commits into
mainfrom
fix/extern-alias-types

Conversation

@vbreuss

@vbreuss vbreuss commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Types from a reference that is only reachable through an extern alias (for example <Aliases>Legacy</Aliases> on a PackageReference/ProjectReference, typically used to consume two versions of the same assembly) do not resolve under global::. The generated mock therefore failed to compile with CS0400, so such types could not be mocked at all.

Changes

  • Types from an assembly that is reachable only through an extern alias are now qualified with that alias (Legacy::Ns.IFoo) instead of global::. This covers mocked types, member signatures, containing types of explicit implementations, generic constraints, enum default values and attribute arguments.
  • Each generated file that uses an alias declares it (extern alias Legacy;) directly after the auto-generated header. Every generator output receives the alias list, so no file can use an alias without declaring it.
  • Assemblies that are also reachable through global (a second reference without an alias, or Aliases="global,Legacy") keep the global:: qualification.
  • Attribute array arguments are now serialized element by element with the same rules as single arguments. Before, enum elements were emitted as plain numbers (new Color[]{1}, CS0266) and Type elements as bare names (new Type[]{string}, CS1525), independent of extern aliases. A null array argument now becomes null.

Tests

  • An extern-aliased reference: interfaces, abstract classes, delegates, Implementing<T>() combinations, indexers, events, enum defaults and attributes (including enum and typeof values inside arrays) are qualified with the alias. Every file using the alias also declares it.
  • A reference that is also reachable through global, as a separate reference and via global,Alias, keeps global::.
  • An unresolved type in a mocked member while an extern alias is present no longer makes the generator throw (CS8785) and drop all other mocks.
  • Attribute array arguments with enum, Type, mixed object[] and null values compile.

Types from a reference that is only reachable through an extern alias do not resolve under `global::`, so the generated mock failed to compile (CS0400). The generator now qualifies such types with their alias and declares it in the generated file.
@vbreuss vbreuss self-assigned this Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Test Results

    31 files  ± 0      31 suites  ±0   48m 15s ⏱️ + 10m 23s
 4 536 tests + 5   4 536 ✅ + 5  0 💤 ±0  0 ❌ ±0 
28 837 runs  +15  28 837 ✅ +15  0 💤 ±0  0 ❌ ±0 

Results for commit 22ade35. ± Comparison against base commit 797cfc2.

This pull request removes 22 and adds 21 tests. Note that renamed tests count towards both.
 x-myHeader3: baz, True)
)
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
Mockolate.Tests.Web.ItExtensionsTests+IsHttpContentTests+WithHeadersTests ‑ ShouldVerifyStringHeaders(x-myHeader1 : foo
Mockolate.Tests.Web.ItExtensionsTests+IsHttpContentTests+WithHeadersTests ‑ ShouldVerifyStringHeaders(x-myHeader1 : foo
…
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
)
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
)
Mockolate.SourceGenerators.Tests.GeneralTests ‑ WithArrayAttributeArguments_ShouldSerializeEachElementByItsKind
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
int GetHashCode();, public override bool Equals(object obj), public override int GetHashCode())
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
int GetHashCode();, public override bool Equals(object obj), public override int GetHashCode())
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object obj);, public override string ToString(), public override bool Equals(object obj))
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object? obj);
int GetHashCode();, public override string ToString(), public override int GetHashCode(), public override bool Equals(object? obj))
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
int GetHashCode();, public override string ToString(), public override int GetHashCode())
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object obj);, public override string ToString(), public override bool Equals(object obj))
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object? obj);
int GetHashCode();, public override string ToString(), public override int GetHashCode(), public override bool Equals(object? obj))
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 9V45 4.37GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Method N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 199.8 ns 4.53 ns 4.02 ns 0.97 1976 B 1.00
Mockolate 1 207.0 ns 5.14 ns 4.55 ns 1.00 1976 B 1.00
Imposter 1 141.8 ns 3.81 ns 3.57 ns 0.69 928 B 0.47
TUnitMocks 1 301.2 ns 11.40 ns 10.10 ns 1.46 2088 B 1.06
Moq 1 58,198.3 ns 1,328.62 ns 1,177.79 ns 281.23 14944 B 7.56
NSubstitute 1 3,421.0 ns 75.32 ns 70.45 ns 16.53 9336 B 4.72
FakeItEasy 1 3,428.2 ns 95.20 ns 89.05 ns 16.57 8244 B 4.17
baseline* 10 457.5 ns 5.51 ns 4.60 ns 0.91 2192 B 1.00
Mockolate 10 502.0 ns 9.73 ns 8.62 ns 1.00 2192 B 1.00
Imposter 10 365.1 ns 8.35 ns 7.81 ns 0.73 1792 B 0.82
TUnitMocks 10 1,035.3 ns 26.78 ns 23.74 ns 2.06 3840 B 1.75
Moq 10 56,967.6 ns 993.54 ns 929.36 ns 113.52 18930 B 8.64
NSubstitute 10 4,761.8 ns 66.17 ns 61.90 ns 9.49 12360 B 5.64
FakeItEasy 10 5,238.5 ns 62.28 ns 55.21 ns 10.44 15786 B 7.20
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 2.61GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

CreateMock Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 18.49 ns 0.421 ns 0.394 ns 1.03 160 B 1.00
Mockolate 18.03 ns 0.863 ns 0.807 ns 1.00 160 B 1.00
Imposter 115.61 ns 1.393 ns 1.235 ns 6.43 576 B 3.60
TUnitMocks 36.95 ns 1.739 ns 1.627 ns 2.05 216 B 1.35
Moq 1,389.58 ns 5.271 ns 4.402 ns 77.23 2096 B 13.10
NSubstitute 2,038.38 ns 30.450 ns 26.993 ns 113.29 5048 B 31.55
FakeItEasy 1,774.55 ns 19.039 ns 17.809 ns 98.62 2763 B 17.27
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 3.04GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Event Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 374.3 ns 5.72 ns 5.35 ns 1.30 1744 B 1.00
Mockolate 287.0 ns 11.32 ns 10.59 ns 1.00 1744 B 1.00
Imposter 188.5 ns 4.30 ns 4.02 ns 0.66 512 B 0.29
TUnitMocks 192.1 ns 7.25 ns 6.79 ns 0.67 1392 B 0.80
Moq 15,784.2 ns 42.94 ns 40.16 ns 55.06 12810 B 7.35
NSubstitute 5,946.4 ns 31.69 ns 29.64 ns 20.74 9264 B 5.31
FakeItEasy 220,082.4 ns 868.45 ns 725.19 ns 767.77 15755 B 9.03
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 3.24GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Property N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 546.3 ns 18.66 ns 17.45 ns 0.90 2472 B 1.00
Mockolate 1 606.0 ns 11.03 ns 10.32 ns 1.00 2472 B 1.00
Imposter 1 186.0 ns 1.95 ns 1.83 ns 0.31 664 B 0.27
TUnitMocks 1 466.6 ns 5.66 ns 5.02 ns 0.77 1696 B 0.69
Moq 1 12,091.4 ns 90.19 ns 79.95 ns 19.96 10641 B 4.30
NSubstitute 1 8,039.8 ns 87.89 ns 82.21 ns 13.27 11720 B 4.74
FakeItEasy 1 8,977.6 ns 37.20 ns 31.06 ns 14.82 11508 B 4.66
baseline* 10 1,071.9 ns 11.62 ns 9.71 ns 0.94 2976 B 1.00
Mockolate 10 1,141.1 ns 6.93 ns 6.48 ns 1.00 2976 B 1.00
Imposter 10 363.4 ns 2.16 ns 2.02 ns 0.32 952 B 0.32
TUnitMocks 10 1,607.6 ns 19.31 ns 18.07 ns 1.41 4048 B 1.36
Moq 10 19,131.0 ns 83.02 ns 77.65 ns 16.77 18721 B 6.29
NSubstitute 10 18,147.3 ns 87.00 ns 81.38 ns 15.90 21585 B 7.25
FakeItEasy 10 20,723.4 ns 123.50 ns 109.48 ns 18.16 31546 B 10.60
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
INTEL XEON PLATINUM 8573C 3.05GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Callback Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 330.2 ns 5.30 ns 4.70 ns 0.82 1608 B 1.00
Mockolate 401.1 ns 4.42 ns 3.92 ns 1.00 1608 B 1.00
Imposter 266.3 ns 1.20 ns 1.12 ns 0.66 744 B 0.46
TUnitMocks 614.5 ns 5.32 ns 4.98 ns 1.53 2064 B 1.28
Moq 43,867.7 ns 215.45 ns 190.99 ns 109.38 9073 B 5.64
NSubstitute 4,191.4 ns 22.37 ns 19.83 ns 10.45 7896 B 4.91
FakeItEasy 3,804.9 ns 13.67 ns 12.78 ns 9.49 6964 B 4.33
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
INTEL XEON PLATINUM 8573C 3.20GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Indexer N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 946.9 ns 33.78 ns 31.60 ns 0.99 3.81 KB 1.00
Mockolate 1 952.6 ns 23.30 ns 21.79 ns 1.00 3.81 KB 1.00
Imposter 1 349.6 ns 7.96 ns 6.65 ns 0.37 1.19 KB 0.31
Moq 1 98,959.9 ns 832.42 ns 737.92 ns 103.94 20.38 KB 5.35
NSubstitute 1 9,447.5 ns 81.81 ns 76.52 ns 9.92 12.78 KB 3.35
FakeItEasy 1 7,601.3 ns 107.84 ns 100.88 ns 7.98 13.62 KB 3.57
baseline* 10 2,508.8 ns 17.38 ns 16.26 ns 0.94 4.87 KB 1.00
Mockolate 10 2,664.2 ns 21.08 ns 19.72 ns 1.00 4.87 KB 1.00
Imposter 10 815.8 ns 16.03 ns 14.21 ns 0.31 2.24 KB 0.46
Moq 10 103,639.3 ns 787.92 ns 698.47 ns 38.90 29.88 KB 6.14
NSubstitute 10 21,718.6 ns 167.87 ns 148.81 ns 8.15 25.58 KB 5.26
FakeItEasy 10 18,292.6 ns 126.69 ns 112.31 ns 6.87 32.98 KB 6.78

baseline* rows show the corresponding Mockolate benchmark from the most recent successful main branch build with results, for regression comparison.

An unresolved namespace-qualified type in a mocked member made the generator throw whenever an extern alias was present, dropping every mock. Attribute array elements ignored their kind, so non-zero enums and types inside arrays did not compile. Every output now receives the aliases explicitly so no file can miss the directive.
@sonarqubecloud

Copy link
Copy Markdown

@vbreuss
vbreuss merged commit db4bb6c into main Oct 31, 2026
21 checks passed
@vbreuss
vbreuss deleted the fix/extern-alias-types branch October 31, 2026 10:33
github-actions Bot added a commit that referenced this pull request Oct 31, 2026
github-actions Bot added a commit that referenced this pull request Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant