Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<PackageVersion Include="Microsoft.Testing.Platform.MSBuild" Version="2.3.3" />
<PackageVersion Include="Imposter" Version="0.1.9"/>
<PackageVersion Include="Mockolate" Version="3.4.0" />
<PackageVersion Include="Mono.Cecil" Version="0.11.6" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.0" />
<PackageVersion Include="ModularPipelines.DotNet" Version="3.2.8" />
Expand Down
3 changes: 3 additions & 0 deletions TUnit.CI.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Project Path="src/TUnit.Mocks/TUnit.Mocks.csproj" />
<Project Path="src/TUnit.Mocks.Assertions/TUnit.Mocks.Assertions.csproj" />
<Project Path="src/TUnit.Mocks.Http/TUnit.Mocks.Http.csproj" />
<Project Path="src/TUnit.Mocks.InternalsAccess.Tasks/TUnit.Mocks.InternalsAccess.Tasks.csproj" />
<Project Path="src/TUnit.Mocks.Logging/TUnit.Mocks.Logging.csproj" />
<Project Path="src/TUnit.Playwright/TUnit.Playwright.csproj" />
</Folder>
Expand Down Expand Up @@ -76,6 +77,8 @@
<Project Path="tests/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Analyzers.Tests/TUnit.Mocks.Analyzers.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Http.Tests/TUnit.Mocks.Http.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.InternalsAccess.TargetLib/TUnit.Mocks.InternalsAccess.TargetLib.csproj" />
<Project Path="tests/TUnit.Mocks.InternalsAccess.Tests/TUnit.Mocks.InternalsAccess.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Logging.Tests/TUnit.Mocks.Logging.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Tests/TUnit.Mocks.Tests.csproj" />
Expand Down
3 changes: 3 additions & 0 deletions TUnit.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Project Path="src/TUnit.Mocks/TUnit.Mocks.csproj" />
<Project Path="src/TUnit.Mocks.Assertions/TUnit.Mocks.Assertions.csproj" />
<Project Path="src/TUnit.Mocks.Http/TUnit.Mocks.Http.csproj" />
<Project Path="src/TUnit.Mocks.InternalsAccess.Tasks/TUnit.Mocks.InternalsAccess.Tasks.csproj" />
<Project Path="src/TUnit.Mocks.Logging/TUnit.Mocks.Logging.csproj" />
<Project Path="src/TUnit.Playwright/TUnit.Playwright.csproj" />
<Project Path="src/TUnit.Reporting.Tool/TUnit.Reporting.Tool.csproj" />
Expand Down Expand Up @@ -77,6 +78,8 @@
<Project Path="tests/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Analyzers.Tests/TUnit.Mocks.Analyzers.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Http.Tests/TUnit.Mocks.Http.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.InternalsAccess.TargetLib/TUnit.Mocks.InternalsAccess.TargetLib.csproj" />
<Project Path="tests/TUnit.Mocks.InternalsAccess.Tests/TUnit.Mocks.InternalsAccess.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Logging.Tests/TUnit.Mocks.Logging.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj" />
<Project Path="tests/TUnit.Mocks.Tests/TUnit.Mocks.Tests.csproj" />
Expand Down
54 changes: 54 additions & 0 deletions docs/docs/writing-tests/mocking/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,57 @@ mock.Invocations.Count; // 0 (history cleared)
```

The `SetupAllProperties()` flag is preserved across resets.

## Internals Access (experimental)

Some SDKs route behavior through types that are `internal` to their own assembly — the classic
example is `Microsoft.Azure.Functions.Worker`, whose `IInvocationFeatures.Get<T>()` is called
inside the SDK with `T = IFunctionBindingsFeature`, a type your test assembly cannot even name.
Runtime-proxy libraries can auto-substitute such types (when the SDK grants `InternalsVisibleTo`
to Castle's proxy assembly), but they can never let you *configure* one.

TUnit.Mocks can, behind an experimental opt-in:

```xml
<PropertyGroup>
<TUnitMocksExperimentalInternalsAccess>true</TUnitMocksExperimentalInternalsAccess>
</PropertyGroup>

<ItemGroup>
<!-- Simple assembly name of any direct or transitive reference. -->
<TUnitMocksInternalsAccess Include="Microsoft.Azure.Functions.Worker.Core" />
</ItemGroup>
```

Internal types of the listed assemblies then behave like public ones in your test project —
nameable, source-generator mocked, with fully typed setups, matchers, and verification. No
`InternalsVisibleTo` is required from the target assembly:

```csharp
var bindings = IFunctionBindingsFeature.Mock(); // internal to the SDK
bindings.InvocationResult.Returns(myResult);

features.Get<IFunctionBindingsFeature>().Returns(bindings.Object);
features.Get<IFunctionBindingsFeature>().WasCalled(Times.Once);
```

### How it works

At build time, each listed reference is swapped — for the compiler only — with a copy whose
internals are rewritten to public, preserving the assembly identity. The original assembly still
ships and loads; an `IgnoresAccessChecksTo` attribute (honored by the .NET runtime) makes the
compiled IL valid against it at execution time. This is the established "publicizer" pattern
used by several long-lived OSS tools, wired into the TUnit.Mocks package.

### Caveats

- **Experimental.** `IgnoresAccessChecksToAttribute` is honored by the runtime but is not a
documented public contract.
- Not supported on .NET Framework test targets (the runtime there does not honor the attribute);
a build warning is emitted and the pipeline stays inert.
- Works under trimmed publishes; Native AOT is not yet verified.
- If another package already injects an `IgnoresAccessChecksToAttribute` definition into your
compilation (e.g. IgnoresAccessChecksToGenerator), suppress TUnit's copy with
`<TUnitMocksInternalsAccessEmitAttributeDefinition>false</TUnitMocksInternalsAccessEmitAttributeDefinition>`.
- Internal APIs are internal for a reason: they can change in any release of the target package.
Prefer public seams when they exist.
Loading
Loading