Declaring InternalsVisibleTo in the csproj

 
 
  • Gérald Barré

While I prefer testing the public API of an assembly, it is sometimes useful to test implementation details. One attribute I often use is [assembly: InternalsVisibleTo("MyAssembly.Tests")] to allow the test assembly to access internal classes or methods. The conventional place to declare assembly attributes is AssemblyInfo.cs, but SDK-based projects no longer include this file by default. You can still create it yourself, but why not take advantage of the SDK-based project format to declare the attribute directly? It is even possible to add it automatically to all your projects!

In a previous post about Getting the date of build of a .NET assembly at runtime, I showed how to add an assembly attribute via the csproj file. You can use the same approach here:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
    <_Parameter1>ClassLibrary1.Tests</_Parameter1>
    </AssemblyAttribute>
  </ItemGroup>
</Project>

When compiling the project, a file will be generated by MSBuild in the obj folder:

C#
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Reflection;

// 👇 The attribute is here!
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("ClassLibrary1.Tests")]
[assembly: System.Reflection.AssemblyCompanyAttribute("ClassLibrary1")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("ClassLibrary1")]
[assembly: System.Reflection.AssemblyTitleAttribute("ClassLibrary1")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

// Generated by the MSBuild WriteCodeFragment class.

We can take this further by leveraging MSBuild to make it more generic. Test projects often follow a naming convention such as {AssemblyName}.Tests. Since MSBuild exposes the assembly name, we can use it to derive the attribute value:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
    <_Parameter1>$(AssemblyName).Tests</_Parameter1> <!-- We use the value of AssemblyName to declare the value of the attribute -->
    </AssemblyAttribute>
  </ItemGroup>
</Project>

Let's go a step further and add it automatically to all projects, while also supporting custom assembly names. Create a file named Directory.Build.targets at the root of the repository:

ClassLibrary1.sln
Directory.Build.targets
📁ClassLibrary1
 ┗ ClassLibrary1.csproj
📁ClassLibrary1.Tests
 ┗ ClassLibrary1.Tests.csproj

Add the following content to the file:

csproj (MSBuild project file)
<Project>
  <Target Name="AddInternalsVisibleTo" BeforeTargets="BeforeCompile">
    <!-- Add default suffix if there is no InternalsVisibleTo or InternalsVisibleToSuffix defined -->
    <ItemGroup Condition="@(InternalsVisibleToSuffix->Count()) == 0 AND @(InternalsVisibleTo->Count()) == 0">
      <InternalsVisibleToSuffix Include=".Tests" />
    </ItemGroup>

    <!-- Handle InternalsVisibleTo -->
    <ItemGroup Condition="'@(InternalsVisibleTo->Count())' &gt; 0">
      <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
        <_Parameter1>%(InternalsVisibleTo.Identity)</_Parameter1>
      </AssemblyAttribute>
    </ItemGroup>

    <!-- Handle InternalsVisibleToSuffix -->
    <ItemGroup Condition="@(InternalsVisibleToSuffix->Count()) &gt; 0">
      <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
        <_Parameter1>$(AssemblyName)%(InternalsVisibleToSuffix.Identity)</_Parameter1>
      </AssemblyAttribute>
    </ItemGroup>
  </Target>
</Project>

This file is more generic and allows more ways to declare which attributes to generate. You can control it from your csproj:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <InternalsVisibleTo Include="CustomTest1" /> <!-- [assembly: InternalsVisibleTo("CustomTest1")] -->
    <InternalsVisibleTo Include="CustomTest2, PublicKey=abc" /> <!-- [assembly: InternalsVisibleTo("CustomTest2, PublicKey=abc")] -->
    <InternalsVisibleTo Include="$(AssemblyName).Custom" /> <!-- [assembly: InternalsVisibleTo("ClassLibrary1.Custom")] -->

    <InternalsVisibleToSuffix Include=".Tests" /> <!-- [assembly: InternalsVisibleTo("ClassLibrary1.Tests")] -->
    <InternalsVisibleToSuffix Include=".FunctionalTests" /> <!-- [assembly: InternalsVisibleTo("ClassLibrary1.FunctionalTests")] -->
  </ItemGroup>

</Project>

If you don't set anything in your csproj, it will automatically add <InternalsVisibleToSuffix Include=".Tests" />.

You now have a generic way to add InternalsVisibleTo attributes in your projects.

#Package the target file as a NuGet package

If you want to use the targets file across multiple projects, you can wrap it in a NuGet package. I knew this was possible since some packages already do it, but I had no idea how, so I searched online. I found that Thomas Levesque had built something very similar in his GitHub project. His package uses a different approach to declaring the attributes, but it is very similar. I started from his code and adapted it to fit my needs. Here are the code and the package:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">

  <!-- Add the NuGet package -->
  <ItemGroup>
    <PackageReference Include="Meziantou.MSBuild.InternalsVisibleTo" Version="1.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <!-- Declare the InternalVisibleTo attributes to generate -->
  <ItemGroup>
    <InternalsVisibleTo Include="CustomTest1" />
    <InternalsVisibleTo Include="CustomTest2" />

    <InternalsVisibleToSuffix Include=".Tests" />
    <InternalsVisibleToSuffix Include=".FunctionalTests" />
  </ItemGroup>

</Project>

#Update: .NET 5

Starting with .NET 5, you can use the <InternalsVisibleTo> without adding any NuGet package:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <InternalsVisibleTo Include="$(AssemblyName).Tests" />
  </ItemGroup>
</Project>

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?