SuperAPI AI Directory

dotnet-testing-private-internal-testing

Testing & quality

View repository

Updated 10-15 23:45

About this skill

A Guide to Testing Strategies for Private and Internal Members

This skill helps you correctly handle the testing of private and internal members in .NET testing, emphasizing a design-first testing mindset.

Response Strategy

When responding, you must fully cover the following three paths so that users can choose the most suitable approach based on their actual situation:

  1. Design-first (refactoring) — Extract complex private logic into a separate class or strategy pattern so that it becomes testable through a public API
  2. InternalsVisibleTo configuration — Change private to internal and use InternalsVisibleTo to expose it for access by the test project
  3. Reflection testing (transitional approach) — Use reflection to directly test private methods, suitable for legacy systems that are difficult to refactor in the short term

Do not recommend only one of these while ignoring the others. Even if refactoring is the primary recommendation, you must still explain the approaches and applicable timing for InternalsVisibleTo and reflection.

Core Principle: Design-First Thinking

Golden Rule

Good design naturally comes with good testability. If you find that you frequently need to test private methods, it is very likely that there is a design problem. However, in practice, not all situations can be refactored immediately, so it is also necessary to understand alternative approaches such as InternalsVisibleTo and reflection testing.

Signs of Design Problems

  • A private method exceeds 10 lines and contains complex logic
  • A private method contains important business rules
  • A private method is difficult to test indirectly through public methods
  • A class takes on multiple responsibilities

Solution: Refactor Instead of Test

Extract complex private logic into a separate class (separation of responsibilities), or use the strategy pattern to inject the calculation logic as a testable public interface. Includes an OrderProcessor refactoring example, a PricingService strategy pattern refactoring (before/after refactoring), and a partial mock example.

For complete refactoring examples and strategy pattern code, refer to references/refactoring-patterns.md

Internal Member Testing Strategy

When Internal Members Need to Be Tested

Suitable situations: Framework or class library development, verification of complex internal algorithms, performance-critical internal components, security-related internal logic

Unsuitable situations: Business logic in the application layer (should be public), simple helper methods, logic that can be tested indirectly through a public API

Approach 1: Use the InternalsVisibleTo Attribute

// 在主專案中的 AssemblyInfo.cs 或任何類別檔案中
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("YourProject.Tests")]
[assembly: InternalsVisibleTo("YourProject.IntegrationTests")]

Approach 2: Configure in csproj

<!-- YourProject.csproj -->
<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
    <_Parameter1>$(AssemblyName).Tests</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

Approach 3: Use Meziantou.MSBuild.InternalsVisibleTo (recommended)

<!-- YourProject.csproj -->
<ItemGroup>
  <PackageReference Include="Meziantou.MSBuild.InternalsVisibleTo" Version="1.0.2">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  </PackageReference>
</ItemGroup>

<ItemGroup>
  <InternalsVisibleTo Include="$(AssemblyName).Tests" />
  <InternalsVisibleTo Include="$(AssemblyName).IntegrationTests" />
  <InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="002400000480000094..." />
</ItemGroup>

Reference resources:

Risk Assessment for Internal Testing

Assessment Aspect Risk Level Description
Encapsulation damage Medium Increases the test's dependence on the internal implementation
Refactoring resistance High Changing internal members affects the tests
Maintenance cost Medium Production code and test code need to be maintained in sync
Design quality Low If overused, it may indicate a design problem

Private method testing techniques

Covers decision tree (whether private methods should be tested), reflection testing of private instance methods and static methods,ReflectionTestHelper helper class encapsulation, and the risks and best practices of reflection testing.

For complete code examples and technical details, please refer to references/private-method-testing.md

Practical decision framework

Three-level risk assessment method

  1. Design quality assessment — Is this a design problem or a testing problem? Prioritize refactoring (extract class, strategy pattern)
  2. Maintenance cost assessment — Will the tests become an obstacle to refactoring? If maintenance cost is high, reconsider the testing strategy
  3. Value output assessment — Does the value brought by the tests exceed the cost? If the value is insufficient, look for an alternative testing strategy

Decision matrix

Scenario Recommended approach Rationale
Simple private methods (< 10 lines) Test through public methods Low maintenance cost
Complex private logic (> 10 lines) Refactor into a separate class Improves design and testability
Framework internal algorithms Use InternalsVisibleTo Need to precisely test internal behavior
Private methods in legacy systems Consider using reflection testing Difficult to refactor in the short term
Security-related private logic Refactor or use reflection testing Need independent verification of correctness
Frequently changing implementation details Avoid direct testing Tests will become fragile

Best practices

  1. Design first — Needing to test private methods usually means the class has taken on too many responsibilities. Prioritize refactoring (dependency injection, strategy pattern, separation of responsibilities) so that the logic is naturally testable through the public API.
  2. Test public behavior — Focus on testing the behavior of the public API, and indirectly cover private logic through public methods.
  3. Use InternalsVisibleTo wisely — Use only in framework or class library development scenarios. Use Meziantou.MSBuild.InternalsVisibleTo to simplify configuration.
  4. Use reflection cautiously — Reflection is a last resort; create helper methods to encapsulate reflection logic to reduce maintenance costs.

Common misconceptions

  1. Over-testing private methods — Writing tests for every private method makes the test suite fragile.
  2. Ignoring design problems — Using testing techniques to bypass encapsulation only treats the symptoms, not the root cause.
  3. Depending on implementation details — Testing the call order of private methods tightly couples tests to the implementation.
  4. Abusing InternalsVisibleTo — Exposing internal for application-layer code breaks encapsulation boundaries.

Output format

The reply must include the following three sections (arranged in priority order, but all must be mentioned):

  1. Refactoring recommendations — Explains how to make logic testable through design improvements, with code examples
  2. InternalsVisibleTo approach — Explains three configuration methods, with configuration examples
  3. Reflection testing approach — Explains how to test private methods using reflection (as a transitional approach), with code examples

Reference resources

Original article

The content of this skill is distilled from the "Old-School Software Engineer's Testing Practice - 30-Day Challenge" series of articles:

Official documentation

Related skills

  • unit-test-fundamentals - Unit testing fundamentals
  • nsubstitute-mocking - Test doubles and mocking

Testing checklist

  • Have evaluated whether refactoring should be done instead of testing private methods
  • Internal members truly need to be exposed to the test project
  • Use an appropriate InternalsVisibleTo configuration method
  • Reflection testing has been encapsulated using helper methods
  • Test names clearly indicate the test type
  • Tests will not become an obstacle to refactoring
  • The value provided by the tests exceeds the maintenance cost