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:
- Design-first (refactoring) — Extract complex private logic into a separate class or strategy pattern so that it becomes testable through a public API
- InternalsVisibleTo configuration — Change private to internal and use InternalsVisibleTo to expose it for access by the test project
- 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:
- Declaring InternalsVisibleTo in the csproj - Meziantou's blog
- GitHub - meziantou/Meziantou.MSBuild.InternalsVisibleTo
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
- Design quality assessment — Is this a design problem or a testing problem? Prioritize refactoring (extract class, strategy pattern)
- Maintenance cost assessment — Will the tests become an obstacle to refactoring? If maintenance cost is high, reconsider the testing strategy
- 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
- 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.
- Test public behavior — Focus on testing the behavior of the public API, and indirectly cover private logic through public methods.
- Use InternalsVisibleTo wisely — Use only in framework or class library development scenarios. Use Meziantou.MSBuild.InternalsVisibleTo to simplify configuration.
- Use reflection cautiously — Reflection is a last resort; create helper methods to encapsulate reflection logic to reduce maintenance costs.
Common misconceptions
- Over-testing private methods — Writing tests for every private method makes the test suite fragile.
- Ignoring design problems — Using testing techniques to bypass encapsulation only treats the symptoms, not the root cause.
- Depending on implementation details — Testing the call order of private methods tightly couples tests to the implementation.
- 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):
- Refactoring recommendations — Explains how to make logic testable through design improvements, with code examples
- InternalsVisibleTo approach — Explains three configuration methods, with configuration examples
- 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:
- Day 09 - Testing private and internal members: Testing strategies for Private and Internal
- Ironman series article:https://ithelp.ithome.com.tw/articles/10374866
- Sample code:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day09
Official documentation
Related skills
unit-test-fundamentals- Unit testing fundamentalsnsubstitute-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