Using InternalsVisibleTo in C# Unit Testing
This article provides an overview of the
InternalsVisibleTo attribute in C#, explaining how it
grants unit test projects access to internal types and
members without compromising public API design. You will learn the
mechanics behind assembly friendship, modern configuration techniques in
.NET project files, how to handle strongly-named
assemblies, and best practices for testing non-public code.
What Is InternalsVisibleTo?
In C#, the internal access modifier restricts visibility
to code within the same assembly. However, unit tests typically live in
a separate test assembly. By default, this prevents unit tests from
instantiating internal classes or invoking internal methods
directly.
The InternalsVisibleTo attribute, located in the
System.Runtime.CompilerServices namespace, resolves this
limitation. It designates one or more assemblies as "friend assemblies,"
granting them access to all internal types and members of
the target assembly while keeping those elements hidden from regular
consumers and external libraries.
Why Use It in Unit Testing?
Exposing implementation details using the public
modifier solely to make them testable degrades library design and
pollutes the public API surface. Using InternalsVisibleTo
maintains proper encapsulation:
- Encapsulation Protection: Keeps internal business rules, data-access helpers, and pipeline handlers hidden from third-party consumers.
- Refactoring Safety: Internal components can be tested in isolation before being integrated into broader workflows.
- Cleaner Public APIs: Consumers only see the intended entry points of your library or application.
How to Configure InternalsVisibleTo
There are two primary ways to expose internal members to a test assembly in modern .NET projects.
1. In the Project File (.csproj)
In .NET 5 and later, the recommended approach is adding the attribute
directly inside the production project’s .csproj file. This
eliminates the need for manual AssemblyInfo.cs files:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MyApplication.UnitTests" />
</ItemGroup>
</Project>2. In an AssemblyInfo.cs File
Alternatively, you can apply the attribute at the assembly level in a
C# source file (conventionally named AssemblyInfo.cs):
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("MyApplication.UnitTests")]Signed (Strong-Named) Assemblies
If your production assembly is signed with a strong name key
(.snk), the friend assembly declared in
InternalsVisibleTo must also be signed, and you must
include the full public key (not the public key token) in the
declaration:
[assembly: InternalsVisibleTo("MyApplication.UnitTests, PublicKey=002400000480000094000000060200000024000052534131...")]To extract the full public key string, use the Strong Name tool
(sn.exe) provided with the .NET SDK:
sn -tp MyApplication.UnitTests.dllBest Practices
- Test Behaviors Over Pure State: Use
InternalsVisibleTofor cohesive, internal architectural components (such as an internal domain service or complex internal parser), not merely to inspect trivial private fields. - Avoid Exposing to Multiple Production Assemblies: Keep the friend relationship reserved almost exclusively for test assemblies to prevent tight coupling between distinct production components.
- Consider Visible Internal Interfaces: If a class is
difficult to test, consider abstracting it behind an
internal interfacethat can be easily mocked in your test project using mocking frameworks like Moq or NSubstitute.