Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.
Private and Internal Member Testing Strategy Guide
This skill helps you properly handle testing of private and internal members in .NET testing, emphasizing design-first
testing thinking.
Applicable Scenarios
Use this skill when asked to perform the following tasks:
- Test private or internal methods and properties
- Configure InternalsVisibleTo to access internal members
- Evaluate whether to test private methods or refactor design
- Use Reflection to access private members
- Improve code testability design
Core Principles: Design-First Thinking
Golden Rule
Good design naturally has good testability. If you find yourself frequently needing to test private methods, the design is likely problematic
Signs of Design Problems
When you want to test private methods, first check for these signs:
- ❌ Private methods over 10 lines with complex logic
- ❌ Private methods contain important business rules
- ❌ Private methods difficult to test indirectly through public methods
- ❌ Class has multiple responsibilities
Solution: Refactor Rather Than Test
// ❌ Problematic design
public class OrderProcessor
{
public OrderResult ProcessOrder(Order order)
{
// Uses multiple complex private methods
var discount = CalculateDiscount(order); // 20 lines logic
var tax = CalculateTax(order, discount); // 15 lines logic
// ...
}
private decimal CalculateDiscount(Order order) { /* complex logic */ }
private decimal CalculateTax(Order order, decimal discount) { /* complex logic */ }
}
// ✅ Improved design: Separation of concerns
public class OrderProcessor
{
private readonly IDiscountCalculator _discountCalculator;
private readonly ITaxCalculator _taxCalculator;
public OrderProcessor(
IDiscountCalculator discountCalculator,
ITaxCalculator taxCalculator)
{
_discountCalculator = discountCalculator;
_taxCalculator = taxCalculator;
}
public OrderResult ProcessOrder(Order order)
{
var discount = _discountCalculator.Calculate(order);
var tax = _taxCalculator.Calculate(order, discount);
// ...
}
}
// Now each calculator can be tested independently
public class DiscountCalculator : IDiscountCalculator
{
public decimal Calculate(Order order)
{
// Complex logic is now public method, easy to test
}
}
```text
## Internal Member Testing Strategy
### When to Test Internal Members
### Appropriate Scenarios
- ✅ Framework or class library development
- ✅ Complex internal algorithm validation
- ✅ Performance-critical internal components
- ✅ Security-related internal logic
### Inappropriate Scenarios
- ❌ Application layer business logic (should be public)
- ❌ Simple helper methods
- ❌ Logic that can be tested indirectly through public API
### Method 1: Using InternalsVisibleTo Attribute
Most direct method, suitable for simple cases:
```csharp
// In main project AssemblyInfo.cs or any class file
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("YourProject.Tests")]
[assembly: InternalsVisibleTo("YourProject.IntegrationTests")]
```text
### Pros
- Simple and direct
- No additional packages needed
### Cons
- Requires hardcoded assembly names
- For signed assemblies, need to include public key
### Method 2: Configuring in csproj
Configure via MSBuild properties:
```xml
<!-- YourProject.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
```text
### Pros: (continued)
- Can use MSBuild variables
- Centralized management
### Method 3: Using Meziantou.MSBuild.InternalsVisibleTo (Recommended)
For complex projects, recommend using this NuGet package:
```xml
<!-- 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="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup>
```text
### Pros: (continued)
- Automatically handles public keys for signed assemblies
- Supports DynamicProxyGenAssembly2 (NSubstitute/Moq)
- High readability
### Reference Resources
- [Declaring InternalsVisibleTo in the csproj - Meziantou's blog](https://www.meziantou.net/declaring-internalsvisibleto-in-the-csproj.htm)
- [GitHub - meziantou/Meziantou.MSBuild.InternalsVisibleTo](https://github.com/meziantou/Meziantou.MSBuild.InternalsVisibleTo)
### Internal Testing Risk Assessment
| Assessment Aspect | Risk Level | Description |
| :---------------- | :--------- | :--------------------------------------- |
| Encapsulation Break | Medium | Increases test dependency on internal implementation |
| Refactoring Resistance | High | Changing internal members affects tests |
| Maintenance Cost | Medium | Need to maintain both production and test code |
| Design Quality | Low | If overused, may indicate design problems |
## Private Method Testing Techniques
Covers decision tree (whether to test private methods), reflection testing of private instance and static methods, `ReflectionTestHelper` helper class encapsulation, and risks and best practices of reflection testing.
> For complete code examples and technical details, see [references/private-method-testing.md](references/private-method-testing.md)
## Test-Friendly Design Patterns
### Strategy Pattern to Improve Testability
Refactor complex private logic into strategy pattern:
#### Before Refactoring: Hard to Test Design
```csharp
public class PricingService
{
public decimal CalculatePrice(Product product, Customer customer)
{
var basePrice = product.BasePrice;
var discount = CalculateDiscount(customer, product); // private method
var tax = CalculateTax(product, customer.Location); // private method
return basePrice - discount + tax;
}
private decimal CalculateDiscount(Customer customer, Product product)
{
// 20 lines complex discount calculation logic
}
private decimal CalculateTax(Product product, Location location)
{
// 15 lines complex tax calculation
}
}
```text
#### After Refactoring: Using Strategy Pattern
```csharp
// Strategy interface
public interface IDiscountStrategy
{
decimal Calculate(Customer customer, Product product);
}
public interface ITaxStrategy
{
decimal Calculate(Product product, Location location);
}
// Concrete strategy implementations
public class StandardDiscountStrategy : IDiscountStrategy
{
public decimal Calculate(Customer customer, Product product)
{
// Discount logic is now public method, easy to test
if (customer.IsVIP)
return product.BasePrice * 0.1m;
return 0;
}
}
public class TaiwanTaxStrategy : ITaxStrategy
{
public decimal Calculate(Product product, Location location)
{
// Tax logic is now public method, easy to test
return product.BasePrice * 0.05m;
}
}
// Improved service
public class PricingService
{
private readonly IDiscountStrategy _discountStrategy;
private readonly ITaxStrategy _taxStrategy;
public PricingService(
IDiscountStrategy discountStrategy,
ITaxStrategy taxStrategy)
{
_discountStrategy = discountStrategy;
_taxStrategy = taxStrategy;
}
public decimal CalculatePrice(Product product, Customer customer)
{
var basePrice = product.BasePrice;
var discount = _discountStrategy.Calculate(customer, product);
var tax = _taxStrategy.Calculate(product, customer.Location);
return basePrice - discount + tax;
}
}
```text
### Pros: (continued)
- Each strategy can be tested independently
- Follows Open/Closed Principle
- Easy to extend new strategies
- Reduces dependency on reflection
### Partial Mock
Sometimes need to mock partial behavior of a class:
```csharp
// Class needing partial mock
public class DataProcessor
{
public ProcessResult Process(string input)
{
var validated = ValidateInput(input);
if (!validated)
return ProcessResult.InvalidInput();
var data = TransformData(input);
var saved = SaveData(data); // Want to mock this method to avoid actual database operations
return saved
? ProcessResult.Success()
: ProcessResult.Failed();
}
protected virtual bool SaveData(string data)
{
// Actual database operation
return true;
}
private bool ValidateInput(string input) => !string.IsNullOrEmpty(input);
private string TransformData(string input) => input.ToUpper();
}
// Test subclass
public class TestableDataProcessor : DataProcessor
{
protected override bool SaveData(string data)
{
// Mock implementation, avoid actual database operations
return true;
}
}
// Test
[Fact]
public void Process_Using_Partial_Mock_Should_Process_Successfully()
{
// Arrange
var processor = new TestableDataProcessor();
// Act
var result = processor.Process("test");
// Assert
result.Success.Should().BeTrue();
}
```text
## Practical Decision Framework
### Three-Level Risk Assessment
#### Level 1: Design Quality Assessment
### Question: Is this a design problem or a testing problem?
- Are private methods too complex? (> 10 lines)
- Does the class have multiple responsibilities?
- Can it be extracted as an independent class?
### Recommended Action
- Prioritize refactoring (extract class, strategy pattern)
- Improve testability through improved design
#### Level 2: Maintenance Cost Assessment
### Question: Will testing become a hindrance to refactoring?
- Does the test depend on implementation details?
- Will the test need significant modification when refactoring?
- Is it difficult to locate problems when tests fail?
### Recommended Action: (continued)
- If maintenance cost is high, reconsider testing strategy
- Consider integration testing through public API
#### Level 3: Value Output Assessment
## Detailed Examples
See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.
1---2name: dotnet-testing-private-internal-testing3category: testing4subcategory: specialized5description: |6 Guide for Private and Internal member testing strategies. Use when you need to test private or internal members, configure InternalsVisibleTo, or evaluate testability design. Covers design-first thinking, reflection testing, strategy pattern refactoring, AbstractLogger pattern, and decision frameworks.7 Keywords: private method testing, internal testing, InternalsVisibleTo, reflection testing, GetMethod BindingFlags, Meziantou.MSBuild.InternalsVisibleTo, testability design, strategy pattern refactoring, testability8targets: ['*']9license: MIT10metadata:11 author: Kevin Tseng12 version: '1.0.0'13 tags: 'private-testing, internal-testing, InternalsVisibleTo, reflection, testability, design'14 related_skills: 'nsubstitute-mocking, unit-test-fundamentals, test-naming-conventions'15claudecode: {}16opencode: {}17codexcli:18 short-description: '.NET skill guidance for dotnet-testing-private-internal-testing'19copilot: {}20geminicli: {}21antigravity: {}22---2324Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.2526# Private and Internal Member Testing Strategy Guide2728This skill helps you properly handle testing of private and internal members in .NET testing, emphasizing design-first29testing thinking.3031## Applicable Scenarios3233Use this skill when asked to perform the following tasks:3435- Test private or internal methods and properties36- Configure InternalsVisibleTo to access internal members37- Evaluate whether to test private methods or refactor design38- Use Reflection to access private members39- Improve code testability design4041## Core Principles: Design-First Thinking4243### Golden Rule4445### Good design naturally has good testability. If you find yourself frequently needing to test private methods, the design is likely problematic4647### Signs of Design Problems4849When you want to test private methods, first check for these signs:5051- ❌ Private methods over 10 lines with complex logic52- ❌ Private methods contain important business rules53- ❌ Private methods difficult to test indirectly through public methods54- ❌ Class has multiple responsibilities5556### Solution: Refactor Rather Than Test5758````csharp59// ❌ Problematic design60public class OrderProcessor61{62 public OrderResult ProcessOrder(Order order)63 {64 // Uses multiple complex private methods65 var discount = CalculateDiscount(order); // 20 lines logic66 var tax = CalculateTax(order, discount); // 15 lines logic67 // ...68 }6970 private decimal CalculateDiscount(Order order) { /* complex logic */ }71 private decimal CalculateTax(Order order, decimal discount) { /* complex logic */ }72}7374// ✅ Improved design: Separation of concerns75public class OrderProcessor76{77 private readonly IDiscountCalculator _discountCalculator;78 private readonly ITaxCalculator _taxCalculator;7980 public OrderProcessor(81 IDiscountCalculator discountCalculator,82 ITaxCalculator taxCalculator)83 {84 _discountCalculator = discountCalculator;85 _taxCalculator = taxCalculator;86 }8788 public OrderResult ProcessOrder(Order order)89 {90 var discount = _discountCalculator.Calculate(order);91 var tax = _taxCalculator.Calculate(order, discount);92 // ...93 }94}9596// Now each calculator can be tested independently97public class DiscountCalculator : IDiscountCalculator98{99 public decimal Calculate(Order order)100 {101 // Complex logic is now public method, easy to test102 }103}104```text105106## Internal Member Testing Strategy107108### When to Test Internal Members109110### Appropriate Scenarios111112- ✅ Framework or class library development113- ✅ Complex internal algorithm validation114- ✅ Performance-critical internal components115- ✅ Security-related internal logic116117### Inappropriate Scenarios118119- ❌ Application layer business logic (should be public)120- ❌ Simple helper methods121- ❌ Logic that can be tested indirectly through public API122123### Method 1: Using InternalsVisibleTo Attribute124125Most direct method, suitable for simple cases:126127```csharp128// In main project AssemblyInfo.cs or any class file129using System.Runtime.CompilerServices;130131[assembly: InternalsVisibleTo("YourProject.Tests")]132[assembly: InternalsVisibleTo("YourProject.IntegrationTests")]133```text134135### Pros136137- Simple and direct138- No additional packages needed139140### Cons141142- Requires hardcoded assembly names143- For signed assemblies, need to include public key144145### Method 2: Configuring in csproj146147Configure via MSBuild properties:148149```xml150<!-- YourProject.csproj -->151<Project Sdk="Microsoft.NET.Sdk">152 <PropertyGroup>153 <TargetFramework>net9.0</TargetFramework>154 </PropertyGroup>155156 <ItemGroup>157 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">158 <_Parameter1>$(AssemblyName).Tests</_Parameter1>159 </AssemblyAttribute>160 </ItemGroup>161</Project>162```text163164### Pros: (continued)165166- Can use MSBuild variables167- Centralized management168169### Method 3: Using Meziantou.MSBuild.InternalsVisibleTo (Recommended)170171For complex projects, recommend using this NuGet package:172173```xml174<!-- YourProject.csproj -->175<ItemGroup>176 <PackageReference Include="Meziantou.MSBuild.InternalsVisibleTo" Version="1.0.2">177 <PrivateAssets>all</PrivateAssets>178 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>179 </PackageReference>180</ItemGroup>181182<ItemGroup>183 <InternalsVisibleTo Include="$(AssemblyName).Tests" />184 <InternalsVisibleTo Include="$(AssemblyName).IntegrationTests" />185 <InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />186</ItemGroup>187```text188189### Pros: (continued)190191- Automatically handles public keys for signed assemblies192- Supports DynamicProxyGenAssembly2 (NSubstitute/Moq)193- High readability194195### Reference Resources196197- [Declaring InternalsVisibleTo in the csproj - Meziantou's blog](https://www.meziantou.net/declaring-internalsvisibleto-in-the-csproj.htm)198- [GitHub - meziantou/Meziantou.MSBuild.InternalsVisibleTo](https://github.com/meziantou/Meziantou.MSBuild.InternalsVisibleTo)199200### Internal Testing Risk Assessment201202| Assessment Aspect | Risk Level | Description |203| :---------------- | :--------- | :--------------------------------------- |204| Encapsulation Break | Medium | Increases test dependency on internal implementation |205| Refactoring Resistance | High | Changing internal members affects tests |206| Maintenance Cost | Medium | Need to maintain both production and test code |207| Design Quality | Low | If overused, may indicate design problems |208209## Private Method Testing Techniques210211Covers decision tree (whether to test private methods), reflection testing of private instance and static methods, `ReflectionTestHelper` helper class encapsulation, and risks and best practices of reflection testing.212213> For complete code examples and technical details, see [references/private-method-testing.md](references/private-method-testing.md)214215## Test-Friendly Design Patterns216217### Strategy Pattern to Improve Testability218219Refactor complex private logic into strategy pattern:220221#### Before Refactoring: Hard to Test Design222223```csharp224public class PricingService225{226 public decimal CalculatePrice(Product product, Customer customer)227 {228 var basePrice = product.BasePrice;229 var discount = CalculateDiscount(customer, product); // private method230 var tax = CalculateTax(product, customer.Location); // private method231 return basePrice - discount + tax;232 }233234 private decimal CalculateDiscount(Customer customer, Product product)235 {236 // 20 lines complex discount calculation logic237 }238239 private decimal CalculateTax(Product product, Location location)240 {241 // 15 lines complex tax calculation242 }243}244```text245246#### After Refactoring: Using Strategy Pattern247248```csharp249// Strategy interface250public interface IDiscountStrategy251{252 decimal Calculate(Customer customer, Product product);253}254255public interface ITaxStrategy256{257 decimal Calculate(Product product, Location location);258}259260// Concrete strategy implementations261public class StandardDiscountStrategy : IDiscountStrategy262{263 public decimal Calculate(Customer customer, Product product)264 {265 // Discount logic is now public method, easy to test266 if (customer.IsVIP)267 return product.BasePrice * 0.1m;268269 return 0;270 }271}272273public class TaiwanTaxStrategy : ITaxStrategy274{275 public decimal Calculate(Product product, Location location)276 {277 // Tax logic is now public method, easy to test278 return product.BasePrice * 0.05m;279 }280}281282// Improved service283public class PricingService284{285 private readonly IDiscountStrategy _discountStrategy;286 private readonly ITaxStrategy _taxStrategy;287288 public PricingService(289 IDiscountStrategy discountStrategy,290 ITaxStrategy taxStrategy)291 {292 _discountStrategy = discountStrategy;293 _taxStrategy = taxStrategy;294 }295296 public decimal CalculatePrice(Product product, Customer customer)297 {298 var basePrice = product.BasePrice;299 var discount = _discountStrategy.Calculate(customer, product);300 var tax = _taxStrategy.Calculate(product, customer.Location);301 return basePrice - discount + tax;302 }303}304```text305306### Pros: (continued)307308- Each strategy can be tested independently309- Follows Open/Closed Principle310- Easy to extend new strategies311- Reduces dependency on reflection312313### Partial Mock314315Sometimes need to mock partial behavior of a class:316317```csharp318// Class needing partial mock319public class DataProcessor320{321 public ProcessResult Process(string input)322 {323 var validated = ValidateInput(input);324 if (!validated)325 return ProcessResult.InvalidInput();326327 var data = TransformData(input);328 var saved = SaveData(data); // Want to mock this method to avoid actual database operations329330 return saved331 ? ProcessResult.Success()332 : ProcessResult.Failed();333 }334335 protected virtual bool SaveData(string data)336 {337 // Actual database operation338 return true;339 }340341 private bool ValidateInput(string input) => !string.IsNullOrEmpty(input);342 private string TransformData(string input) => input.ToUpper();343}344345// Test subclass346public class TestableDataProcessor : DataProcessor347{348 protected override bool SaveData(string data)349 {350 // Mock implementation, avoid actual database operations351 return true;352 }353}354355// Test356[Fact]357public void Process_Using_Partial_Mock_Should_Process_Successfully()358{359 // Arrange360 var processor = new TestableDataProcessor();361362 // Act363 var result = processor.Process("test");364365 // Assert366 result.Success.Should().BeTrue();367}368```text369370## Practical Decision Framework371372### Three-Level Risk Assessment373374#### Level 1: Design Quality Assessment375376### Question: Is this a design problem or a testing problem?377378- Are private methods too complex? (> 10 lines)379- Does the class have multiple responsibilities?380- Can it be extracted as an independent class?381382### Recommended Action383384- Prioritize refactoring (extract class, strategy pattern)385- Improve testability through improved design386387#### Level 2: Maintenance Cost Assessment388389### Question: Will testing become a hindrance to refactoring?390391- Does the test depend on implementation details?392- Will the test need significant modification when refactoring?393- Is it difficult to locate problems when tests fail?394395### Recommended Action: (continued)396397- If maintenance cost is high, reconsider testing strategy398- Consider integration testing through public API399400#### Level 3: Value Output Assessment401402## Detailed Examples403404See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.