Skip to content

[TUnit.Mocks] Experimental internals access: IDE reports false CS0122 because only ReferencePathWithRefAssemblies is publicized #6836

Description

@ANcpLua

Summary

With the experimental compile-time internals access (TUnitMocksExperimentalInternalsAccess + TUnitMocksInternalsAccess, #6514 Tier 2 / #6520), dotnet build succeeds and the test passes, but every IDE / Roslyn-workspace-based tool reports a false CS0122 'X' is inaccessible due to its protection level on the internal type.

Cause: TUnitMocksPublicizeReferences swaps the publicized copy into ReferencePathWithRefAssemblies only. Design-time builds and MSBuildWorkspace resolve references from ReferencePath, which still points at the original, un-publicized assembly.

Repro

SdkLib/Api.cs:

namespace SdkLib;

internal interface IQuotaPolicy
{
    bool Allow(string clientId);
}

Tests/Tests.csproj:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <TUnitMocksExperimentalInternalsAccess>true</TUnitMocksExperimentalInternalsAccess>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="TUnit" Version="1.68.0" />
  <PackageReference Include="TUnit.Mocks" Version="1.68.0" />
</ItemGroup>
<ItemGroup>
  <ProjectReference Include="..\SdkLib\SdkLib.csproj" />
  <TUnitMocksInternalsAccess Include="SdkLib" />
</ItemGroup>

Tests/QuotaTests.cs:

using SdkLib;
using TUnit.Mocks;

public class QuotaTests
{
    [Test]
    public async Task Internal_policy_is_mockable()
    {
        var policy = IQuotaPolicy.Mock();
        policy.Allow(Any()).Returns(false);

        await Assert.That(policy.Object.Allow("acme")).IsFalse();
    }
}

Actual vs expected

dotnet build → 0 warnings, 0 errors. dotnet run → 1/1 passed. Correct.

Loading the same project through Roslyn's MSBuildWorkspace (what OmniSharp and the C# language server use) and asking the compilation for diagnostics:

WORKSPACE-ERROR CS0122 10,21: 'IQuotaPolicy' is inaccessible due to its protection level
error count: 1

In an editor the whole test is underlined red, and a second error appears where the mock is passed on:

CS1503: Argument 1: cannot convert from 'SdkLib.IQuotaPolicy?' to 'SdkLib.IQuotaPolicy?'

Expected: the IDE agrees with the compiler, since the build is authoritative and correct.

Root cause

$ dotnet msbuild Tests/Tests.csproj -t:Compile -p:SkipCompilerExecution=true -p:ProvideCommandLineArgs=true -getItem:ReferencePath
"Identity": "/tmp/iavrepro/SdkLib/bin/Debug/net10.0/SdkLib.dll"                      # original

$ ... -getItem:ReferencePathWithRefAssemblies
"Identity": "obj/Debug/net10.0/tunit-mocks-internals/68fe7f1d/SdkLib.dll"            # publicized

$ dotnet msbuild Tests/Tests.csproj -t:ResolveAssemblyReferencesDesignTime -p:DesignTimeBuild=true -getItem:ReferencePath
"Identity": "/tmp/iavrepro/SdkLib/bin/Debug/net10.0/SdkLib.dll"                      # original

TUnit.Mocks.InternalsAccess.targets does this deliberately, and the comment explains why — ReferencePath has to keep the original so copy-local and deps.json bind to the real assembly at runtime. That reasoning holds for a real build, but it also means no design-time consumer ever sees the publicized copy.

Suggested fix

Swap ReferencePath as well when '$(DesignTimeBuild)' == 'true'. Nothing is copied in a design-time build, so the copy-local / deps.json argument does not apply there, and the editor would then see what the compiler sees.

If that is not workable, it would be worth a line in the docs for the feature, since the failure mode looks exactly like a broken setup.

Versions

  • TUnit / TUnit.Mocks 1.68.0, net10.0, SDK 10.0.401, macOS arm64
  • Same behaviour path in 1.63.0–1.68.0: TUnit.Mocks.InternalsAccess.targets is unchanged across those releases
  • Probe used: Microsoft.CodeAnalysis.Workspaces.MSBuild 4.14.0 + Microsoft.Build.Locator 1.7.8, opening Tests.csproj and printing Compilation.GetDiagnostics() errors

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions