I recently stumbled across a nice generic way to apply the InternalsVisibleTo assembly attribute automatically to all class libraries in my solution.

Adding "InternalsVisibleTo" attributes in project files

I've long used the following snippet in my .csproj class library project files to expose internal classes to unit test projects. The use of the $(AssemblyName) variable makes this a reusable snippet that can be copy/pasted into new projects without modification.

<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).IntegrationTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

I won't get into the caveats of testing internals of class libraries here. It's something that requires careful judgement to ensure that you are testing at the right level of abstraction - but that's a topic for another article.

I recently realised there is an easier way to achieve this in solutions with multiple class libraries that all need the same attributes. To do so we can simply place the above snippet in a Directory.Build.props file.

Directory.Build.props

I have a love/hate relationship with Microsoft's MSBuild ecosystem and its slightly archaic markup and hierarchies of inherited build targets/properties.

As a result, I haven't always made the most of useful available conventions like Directory.Build.props files.

If you're new to this concept, Directory.Build.props files allow you to apply MSBuild properties and common build configuration at a directory level, to multiple projects. They're automatically discovered and applied at build time just by virtue of being present on disk.

So let's say we have a solution structure like this:

├─ src
│ ├─ Acme.MyClassLibrary1
│ │ ├─ Acme.MyClassLibrary1.csproj
│ │ └─ ...
│ └─ Acme.MyClassLibrary2
│ ├─ Acme.MyClassLibrary2.csproj
│ └─ ...
├─ test
│ ├─ Acme.MyClassLibrary1.Tests
│ │ ├─ Acme.MyClassLibrary1.Tests.csproj
│ │ └─ ...
│ ├─ Acme.MyClassLibrary1.IntegrationTests
│ │ ├─ Acme.MyClassLibrary1.IntegrationTests.csproj
│ │ └─ ...
│ └─ Acme.MyClassLibrary2.Tests
│ ├─ Acme.MyClassLibrary2.Tests.csproj
│ └─ ...
└─ MySolution.sln

The examples in this article use my conventions of naming unit test projects with .Tests and .IntegrationTests suffixes. You can obviously tweak the provided examples to suit any project naming conventions you might have.

To automatically make internals visible to corresponding unit/integration test projects in the solution - without requiring configuration on a per-project basis - we just need to add the following Directory.Build.props file in the src folder:

<Project>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).IntegrationTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

This will be applied to all .csproj projects in src/ including any new class libraries added in the future.

Note: The filename Directory.Build.props is case-sensitive. This initially caught me out because I had used a lower case "B" in the filename which worked fine on my case-insensitive Mac but failed to work in my case-sensitive Linux GitHub Actions runners.