Table of Contents
Warning

The following document was generated by AI and HAS NOT YET been reviewed.

Advanced Usage

This section covers advanced scenarios including generic types, static classes, records, structs, and internal member access. These examples demonstrate the full power and flexibility of TDoubles.

Generic Type Mocking

TDoubles generator provides comprehensive support for generic types, including single and multiple type parameters, type constraints, and complex generic scenarios.

Single Generic Type Parameter

using TDoubles;
using System;

// Generic service with one type parameter
public class GenericService<T>
{
    public T GetValue(T input) => input;
    
    public void ProcessValue(T value)
    {
        Console.WriteLine($"Processing: {value}");
    }
    
    public T DefaultValue { get; set; } = default(T)!;
}

// Mock the generic service - note the typeof(GenericService<>) syntax
[Mock(typeof(GenericService<>))]
partial class GenericServiceMock<T>
{
    // Generated implementation will handle all generic operations
}

// Example usage with different type parameters
class Program
{
    static void Main()
    {
        // Create concrete instances for different types
        var stringService = new GenericService<string>();
        var intService = new GenericService<int>();
        
        // Create mocks for different type parameters
        var stringMock = new GenericServiceMock<string>(stringService);
        var intMock = new GenericServiceMock<int>(intService);
        
        Console.WriteLine("=== String Generic Mock ===");
        stringMock.DefaultValue = "default";
        Console.WriteLine($"Default: {stringMock.DefaultValue}");
        Console.WriteLine($"GetValue: {stringMock.GetValue("test")}");
        
        // Override behavior for string type
        stringMock.Overrides.GetValue = (input) => $"Mock: {input}";
        stringMock.Overrides.ProcessValue = (value) => Console.WriteLine($"MOCK PROCESSING: {value}");
        
        Console.WriteLine($"Overridden GetValue: {stringMock.GetValue("test")}");
        stringMock.ProcessValue("hello");
        
        Console.WriteLine("\n=== Integer Generic Mock ===");
        intMock.DefaultValue = 42;
        Console.WriteLine($"Default: {intMock.DefaultValue}");
        Console.WriteLine($"GetValue: {intMock.GetValue(100)}");
        
        // Override behavior for int type
        intMock.Overrides.GetValue = (input) => input * 2;
        intMock.Overrides.ProcessValue = (value) => Console.WriteLine($"MOCK INT PROCESSING: {value}");
        
        Console.WriteLine($"Overridden GetValue: {intMock.GetValue(100)}");
        intMock.ProcessValue(200);
    }
}

Multiple Generic Type Parameters with Constraints

using TDoubles;
using System;

// Generic service with multiple type parameters and constraints
public class MultiGenericService<T, U> where T : class where U : struct
{
    public T Transform(U input) => default(T)!;
    
    public void Process<V>(T item, U value, V extra) where V : new()
    {
        Console.WriteLine($"Processing: {item}, {value}, {extra}");
    }
    
    // Method overloads to test naming conflicts
    public void Process(T item) => Console.WriteLine($"Process single: {item}");
    public void Process(T item, U value) => Console.WriteLine($"Process double: {item}, {value}");
}

// Mock with multiple type parameters - note the typeof(MultiGenericService<,>) syntax
[Mock(typeof(MultiGenericService<,>))]
partial class MultiGenericServiceMock<T, U> where T : class where U : struct
{
    // Generated implementation preserves all type constraints
}

// Example usage
class Program
{
    static void Main()
    {
        var realService = new MultiGenericService<string, int>();
        var mockService = new MultiGenericServiceMock<string, int>(realService);
        
        Console.WriteLine("=== Multi-Generic Mock Testing ===");
        
        // Test default behavior
        string result = mockService.Transform(42);
        Console.WriteLine($"Transform result: {result ?? "null"}");
        
        mockService.Process("test");
        mockService.Process("test", 123);
        mockService.Process("item", 456, new object());
        
        Console.WriteLine("\n=== With Overrides ===");
        
        // Override specific methods
        mockService.Overrides.Transform = (input) => $"Transformed_{input}";
        mockService.Overrides.Process_T = (item) => Console.WriteLine($"MOCK: Single process {item}");
        mockService.Overrides.Process_T_U = (item, value) => Console.WriteLine($"MOCK: Double process {item}, {value}");
        
        string overriddenResult = mockService.Transform(42);
        Console.WriteLine($"Overridden Transform: {overriddenResult}");
        
        mockService.Process("override test");
        mockService.Process("override test", 789);
    }
}

Static Class Mocking

Static classes can be mocked by converting their static methods to instance methods, making them testable without changing your existing code structure.

File System Operations Mock

using TDoubles;
using System;
using System.IO;

// Static class with file operations
[Mock]
public static class FileHelper
{
    public static string ReadFile(string path)
    {
        return File.ReadAllText(path);
    }

    public static void WriteFile(string path, string content)
    {
        File.WriteAllText(path, content);
    }

    public static bool FileExists(string path)
    {
        return File.Exists(path);
    }

    public static string CurrentDirectory { get; set; } = Environment.CurrentDirectory;
}

// Example usage in tests
class Program
{
    static void Main()
    {
        // Create mock instance (no constructor parameter needed for static mocks)
        var fileHelperMock = new FileHelperMock();
        
        Console.WriteLine("=== Static Class Mock Testing ===");
        
        // Test property access
        fileHelperMock.CurrentDirectory = @"C:\temp";
        Console.WriteLine($"Current Directory: {fileHelperMock.CurrentDirectory}");
        
        Console.WriteLine("\n=== Override Static Methods for Testing ===");
        
        // Override ReadFile to return mock data instead of reading actual files
        fileHelperMock.Overrides.ReadFile = (path) => $"Mock content from {path}";
        Console.WriteLine($"ReadFile: {fileHelperMock.ReadFile("test.txt")}");
        
        // Override WriteFile to capture calls without actual file operations
        bool writeFileCalled = false;
        string capturedPath = "";
        string capturedContent = "";
        
        fileHelperMock.Overrides.WriteFile = (path, content) => 
        {
            writeFileCalled = true;
            capturedPath = path;
            capturedContent = content;
            Console.WriteLine($"Mock WriteFile called: {path} -> {content}");
        };
        
        fileHelperMock.WriteFile("output.txt", "Hello World");
        Console.WriteLine($"WriteFile was called: {writeFileCalled}");
        Console.WriteLine($"Captured: {capturedPath} -> {capturedContent}");
        
        // Override FileExists for controlled testing scenarios
        fileHelperMock.Overrides.FileExists = (path) => path.EndsWith(".txt");
        Console.WriteLine($"FileExists (test.txt): {fileHelperMock.FileExists("test.txt")}");
        Console.WriteLine($"FileExists (test.doc): {fileHelperMock.FileExists("test.doc")}");
        
        Console.WriteLine("\n=== Fallback to Original Static Methods ===");
        
        // Clear overrides to test fallback behavior
        fileHelperMock.Overrides.ReadFile = null;
        fileHelperMock.Overrides.WriteFile = null;
        fileHelperMock.Overrides.FileExists = null;
        
        Console.WriteLine("Overrides cleared - methods now delegate to original static implementations");
        // Note: In real scenarios, you might not want to call actual file operations in tests
    }
}

Utility Class Mock

using TDoubles;
using System;

[Mock]
public static class MathHelper
{
    public static double CalculateArea(double radius) => Math.PI * radius * radius;
    
    public static string FormatCurrency(decimal amount) => amount.ToString("C");
    
    public static bool IsEven(int number) => number % 2 == 0;
    
    public static int Counter { get; set; } = 0;
}

// Usage example
class Program
{
    static void TestMathHelper()
    {
        var mathMock = new MathHelperMock();
        
        Console.WriteLine("=== Math Helper Mock ===");
        
        // Test default behavior (delegates to static methods)
        Console.WriteLine($"Area of circle (r=5): {mathMock.CalculateArea(5)}");
        Console.WriteLine($"Currency format: {mathMock.FormatCurrency(123.45m)}");
        Console.WriteLine($"Is 4 even: {mathMock.IsEven(4)}");
        
        // Override for testing edge cases
        mathMock.Overrides.CalculateArea = (radius) => radius <= 0 ? 0 : Math.PI * radius * radius;
        mathMock.Overrides.FormatCurrency = (amount) => amount < 0 ? "NEGATIVE" : amount.ToString("C");
        mathMock.Overrides.IsEven = (number) => false; // Force all numbers to be "odd" for testing
        
        Console.WriteLine("\n=== With Test Overrides ===");
        Console.WriteLine($"Area (r=0): {mathMock.CalculateArea(0)}");
        Console.WriteLine($"Area (r=-1): {mathMock.CalculateArea(-1)}");
        Console.WriteLine($"Currency (-100): {mathMock.FormatCurrency(-100)}");
        Console.WriteLine($"Is 4 even (forced odd): {mathMock.IsEven(4)}");
        
        // Test property
        mathMock.Counter = 42;
        Console.WriteLine($"Counter: {mathMock.Counter}");
    }
}

Record and Struct Mocking

TDoubles generator supports modern C# constructs including records, record structs, and regular structs.

Record Mocking

using TDoubles;
using System;

// Regular record with methods
[Mock]
public record Person(string Name, int Age)
{
    public string GetGreeting() => $"Hello, I'm {Name} and I'm {Age} years old.";
    
    public void Celebrate()
    {
        Console.WriteLine($"{Name} is celebrating!");
    }
}

// Record with additional properties
[Mock]
public record Employee(string Name, string Department)
{
    public string Email { get; set; } = string.Empty;
    public decimal Salary { get; set; }
    
    public string GetFullInfo() => $"{Name} works in {Department} and earns {Salary:C}";
    
    public void UpdateSalary(decimal newSalary)
    {
        Salary = newSalary;
    }
}

// Example usage
class Program
{
    static void Main()
    {
        Console.WriteLine("=== Record Mocking ===");
        
        // Create real record instances
        var realPerson = new Person("Alice", 30);
        var realEmployee = new Employee("Bob", "Engineering");
        
        // Create mocks
        var personMock = new PersonMock(realPerson);
        var employeeMock = new EmployeeMock(realEmployee);
        
        // Test default behavior
        Console.WriteLine($"Person greeting: {personMock.GetGreeting()}");
        personMock.Celebrate();
        
        Console.WriteLine($"Employee info: {employeeMock.GetFullInfo()}");
        
        Console.WriteLine("\n=== With Overrides ===");
        
        // Override record methods
        personMock.Overrides.GetGreeting = () => $"Mock greeting from {personMock.Name}";
        personMock.Overrides.Celebrate = () => Console.WriteLine($"MOCK: {personMock.Name} is having a mock celebration!");
        
        employeeMock.Overrides.GetFullInfo = () => $"MOCK: {employeeMock.Name} is a mock employee";
        employeeMock.Overrides.UpdateSalary = (newSalary) => 
        {
            Console.WriteLine($"MOCK: Would update {employeeMock.Name}'s salary to {newSalary:C}");
            // Note: In real mock, you might want to actually update the property
        };
        
        Console.WriteLine($"Overridden greeting: {personMock.GetGreeting()}");
        personMock.Celebrate();
        
        Console.WriteLine($"Overridden employee info: {employeeMock.GetFullInfo()}");
        employeeMock.UpdateSalary(75000);
    }
}

Record Struct and Regular Struct Mocking

using TDoubles;
using System;

// Record struct with methods
[Mock]
public record struct Coordinate(double X, double Y)
{
    public double DistanceFromOrigin() => Math.Sqrt(X * X + Y * Y);
    
    public void PrintCoordinate()
    {
        Console.WriteLine($"Coordinate: ({X}, {Y})");
    }
}

// Regular struct with methods
[Mock]
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public int ManhattanDistance() => Math.Abs(X) + Math.Abs(Y);
    
    public void Move(int deltaX, int deltaY)
    {
        X += deltaX;
        Y += deltaY;
    }
}

// Read-only struct
[Mock]
public struct ReadOnlyPoint
{
    public int X { get; }
    public int Y { get; }
    
    public ReadOnlyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public double Distance(ReadOnlyPoint other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }
}

// Example usage
class Program
{
    static void Main()
    {
        Console.WriteLine("=== Record Struct Mocking ===");
        
        var realCoordinate = new Coordinate(3.0, 4.0);
        var coordinateMock = new CoordinateMock(realCoordinate);
        
        Console.WriteLine($"Distance from origin: {coordinateMock.DistanceFromOrigin()}");
        coordinateMock.PrintCoordinate();
        
        // Override record struct methods
        coordinateMock.Overrides.DistanceFromOrigin = () => 999.0; // Mock distance
        coordinateMock.Overrides.PrintCoordinate = () => Console.WriteLine("MOCK: Coordinate printing overridden");
        
        Console.WriteLine($"Overridden distance: {coordinateMock.DistanceFromOrigin()}");
        coordinateMock.PrintCoordinate();
        
        Console.WriteLine("\n=== Regular Struct Mocking ===");
        
        var realPoint = new Point(5, 12);
        var pointMock = new PointMock(realPoint);
        
        Console.WriteLine($"Manhattan distance: {pointMock.ManhattanDistance()}");
        pointMock.Move(1, 1);
        Console.WriteLine($"After move - X: {pointMock.X}, Y: {pointMock.Y}");
        
        // Override struct methods
        pointMock.Overrides.ManhattanDistance = () => 100; // Mock distance
        pointMock.Overrides.Move = (deltaX, deltaY) => 
        {
            Console.WriteLine($"MOCK: Would move by ({deltaX}, {deltaY})");
            // Note: Actual movement logic could be implemented here
        };
        
        Console.WriteLine($"Overridden Manhattan distance: {pointMock.ManhattanDistance()}");
        pointMock.Move(2, 3);
        
        Console.WriteLine("\n=== Read-Only Struct Mocking ===");
        
        var realReadOnlyPoint = new ReadOnlyPoint(1, 1);
        var readOnlyPointMock = new ReadOnlyPointMock(realReadOnlyPoint);
        
        var otherPoint = new ReadOnlyPoint(4, 5);
        Console.WriteLine($"Distance to other point: {readOnlyPointMock.Distance(otherPoint)}");
        
        // Override read-only struct method
        readOnlyPointMock.Overrides.Distance = (other) => 42.0; // Mock distance
        
        Console.WriteLine($"Overridden distance: {readOnlyPointMock.Distance(otherPoint)}");
    }
}

IncludeInternals Configuration

The IncludeInternals parameter allows you to control whether internal members are included in the generated mock, enabling comprehensive testing of internal APIs.

Basic IncludeInternals Usage

using TDoubles;
using System;

// Service with mixed accessibility levels
public class InternalService
{
    public string PublicMethod() => "public method";
    internal string InternalMethod() => "internal method";
    
    public string PublicProperty { get; set; } = "public property";
    internal string InternalProperty { get; set; } = "internal property";
}

// Mock that includes internal members
[Mock(typeof(InternalService), IncludeInternals = true)]
partial class InternalServiceMockWithInternals
{
    // Generated mock will include both public and internal members
}

// Mock that excludes internal members (default behavior)
[Mock(typeof(InternalService), IncludeInternals = false)]
partial class InternalServiceMockPublicOnly
{
    // Generated mock will only include public members
}

// Alternative: Mock without specifying IncludeInternals (defaults to false)
[Mock(typeof(InternalService))]
partial class InternalServiceMockDefault
{
    // Generated mock will only include public members (default)
}

// Example usage
class Program
{
    static void Main()
    {
        var realService = new InternalService();
        
        Console.WriteLine("=== Mock with Internal Members ===");
        var mockWithInternals = new InternalServiceMockWithInternals(realService);
        
        // Can access both public and internal members
        Console.WriteLine($"Public method: {mockWithInternals.PublicMethod()}");
        Console.WriteLine($"Internal method: {mockWithInternals.InternalMethod()}");
        Console.WriteLine($"Public property: {mockWithInternals.PublicProperty}");
        Console.WriteLine($"Internal property: {mockWithInternals.InternalProperty}");
        
        // Override both public and internal members
        mockWithInternals.Overrides.PublicMethod = () => "MOCK: public method";
        mockWithInternals.Overrides.InternalMethod = () => "MOCK: internal method";
        
        Console.WriteLine($"Overridden public: {mockWithInternals.PublicMethod()}");
        Console.WriteLine($"Overridden internal: {mockWithInternals.InternalMethod()}");
        
        Console.WriteLine("\n=== Mock with Public Members Only ===");
        var mockPublicOnly = new InternalServiceMockPublicOnly(realService);
        
        // Can only access public members
        Console.WriteLine($"Public method: {mockPublicOnly.PublicMethod()}");
        Console.WriteLine($"Public property: {mockPublicOnly.PublicProperty}");
        
        // The following would cause compilation errors:
        // mockPublicOnly.InternalMethod(); // ❌ Not available
        // mockPublicOnly.InternalProperty; // ❌ Not available
        
        // Can only override public members
        mockPublicOnly.Overrides.PublicMethod = () => "MOCK: public only";
        Console.WriteLine($"Overridden public: {mockPublicOnly.PublicMethod()}");
    }
}

Advanced IncludeInternals Scenarios

using TDoubles;
using System;

// Complex service with mixed accessibility levels
public class ComplexService
{
    public string PublicField = "public field";
    internal string InternalField = "internal field";
    
    public virtual string VirtualPublicMethod() => "virtual public";
    internal virtual string VirtualInternalMethod() => "virtual internal";
    
    public string PublicProperty { get; set; } = "public prop";
    internal string InternalProperty { get; set; } = "internal prop";
    
    public string PublicGetInternalSet { get; internal set; } = "mixed accessibility";
    
    protected string ProtectedMethod() => "protected"; // Not included even with IncludeInternals
    private string PrivateMethod() => "private"; // Never included
}

// Mock with internal access for comprehensive testing
[Mock(typeof(ComplexService), IncludeInternals = true)]
partial class ComplexServiceMock
{
    // Will include public and internal members, but not protected/private
}

// Example for testing internal APIs
class InternalApiTester
{
    public void TestInternalBehavior()
    {
        var realService = new ComplexService();
        var mock = new ComplexServiceMock(realService);
        
        Console.WriteLine("=== Testing Internal API Access ===");
        
        // Test internal field access
        Console.WriteLine($"Internal field: {mock.InternalField}");
        mock.InternalField = "modified internal field";
        Console.WriteLine($"Modified internal field: {mock.InternalField}");
        
        // Test internal method
        Console.WriteLine($"Internal method: {mock.VirtualInternalMethod()}");
        
        // Test internal property
        Console.WriteLine($"Internal property: {mock.InternalProperty}");
        mock.InternalProperty = "modified internal property";
        Console.WriteLine($"Modified internal property: {mock.InternalProperty}");
        
        // Test mixed accessibility property
        Console.WriteLine($"Mixed accessibility: {mock.PublicGetInternalSet}");
        mock.PublicGetInternalSet = "modified mixed"; // Internal setter available
        Console.WriteLine($"Modified mixed: {mock.PublicGetInternalSet}");
        
        Console.WriteLine("\n=== Override Internal Members ===");
        
        // Override internal members for testing
        mock.Overrides.VirtualInternalMethod = () => "MOCK: internal method override";
        
        Console.WriteLine($"Overridden internal method: {mock.VirtualInternalMethod()}");
        
        // Test that protected/private members are not available
        // The following would cause compilation errors:
        // mock.ProtectedMethod(); // ❌ Not available
        // mock.PrivateMethod(); // ❌ Not available
    }
}

// Usage in unit tests
class UnitTestExample
{
    public void TestServiceWithInternalDependencies()
    {
        // This pattern is useful when testing classes that depend on internal APIs
        var mockService = new ComplexServiceMock(new ComplexService());
        
        // Override internal behavior for isolated testing
        mockService.Overrides.VirtualInternalMethod = () => "test internal result";
        mockService.InternalProperty = "test internal state";
        
        // Now you can test how your code behaves with specific internal states
        // without needing to expose internal members publicly
        
        Console.WriteLine("=== Unit Test Scenario ===");
        Console.WriteLine($"Test internal result: {mockService.VirtualInternalMethod()}");
        Console.WriteLine($"Test internal state: {mockService.InternalProperty}");
    }
}

Best Practices for Advanced Usage

Generic Type Naming

When mocking generic types, use descriptive names for your mock classes:

// ✅ Good: Descriptive names
[Mock(typeof(Repository<>))]
partial class RepositoryMock<T> { }

[Mock(typeof(Cache<,>))]
partial class CacheMock<TKey, TValue> where TKey : notnull { }

// ❌ Avoid: Generic names that don't indicate purpose
[Mock(typeof(Service<>))]
partial class ServiceMockT1<T> { } // Less clear what this mocks

Static Class Organization

Group related static method mocks together:

// ✅ Good: Focused static class mocks
[Mock] public static class FileOperations { /* file methods */ }
[Mock] public static class NetworkOperations { /* network methods */ }
[Mock] public static class CryptoOperations { /* crypto methods */ }

// ❌ Avoid: Overly broad static classes
[Mock] public static class Utilities { /* everything mixed together */ }

IncludeInternals Usage Guidelines

Use IncludeInternals = true judiciously:

// ✅ Good: Use for testing internal APIs that need mocking
[Mock(typeof(InternalService), IncludeInternals = true)]
partial class InternalServiceMock { }

// ✅ Good: Default behavior for public APIs
[Mock(typeof(PublicService))]
partial class PublicServiceMock { }

// ⚠️ Consider: Only include internals when necessary for testing
// Including internals increases the mock surface area and coupling

Performance Considerations

  • Generic Mocks: Each generic type instantiation creates a separate mock class, which is normal and expected
  • Static Mocks: Have minimal overhead since they convert static calls to instance calls
  • Struct Mocks: May involve boxing/unboxing, but the generator optimizes for performance
  • IncludeInternals: Slightly increases generated code size but has no runtime performance impact

Performance Considerations and Optimization Tips

Compile-Time Performance

Mock Generation Speed:

  • The source generator runs during compilation and adds minimal overhead
  • Large numbers of mocks (100+) may slightly increase build time
  • Generic mocks with complex constraints may take longer to analyze

Optimization Tips:

// ✅ Prefer specific interfaces over large classes when possible
[Mock(typeof(IUserRepository))]  // Fast - small interface
partial class UserRepositoryMock { }

// ⚠️ Large classes take longer to analyze
[Mock(typeof(MassiveServiceWithHundredsOfMethods))]  // Slower
partial class MassiveServiceMock { }

Runtime Performance

Zero Reflection Overhead:

  • Generated mocks use direct method calls, not reflection
  • Performance is identical to hand-written wrapper classes
  • No runtime type checking or dynamic method invocation

Memory Usage:

  • Each mock instance holds a reference to the target instance
  • Override delegates are stored as fields (minimal memory impact)
  • Generic mocks create separate types per type argument combination

Performance Comparison:

// Traditional reflection-based mocking (slow)
var traditionalMock = new Mock<IUserService>();
traditionalMock.Setup(x => x.GetUser(It.IsAny<int>())).Returns(new User());

// TDoubles (fast - no reflection)
var realService = new UserService();
var generatedMock = new UserServiceMock(realService);
generatedMock.MockOverrides.GetUser = (id) => new User { Id = id };

Optimization Guidelines

  1. Minimize Override Usage: Only override methods you need to customize
  2. Reuse Mock Instances: Create mock instances once and reuse them across tests
  3. Prefer Interfaces: Mock interfaces when possible - they generate smaller, faster code
  4. Avoid Deep Inheritance: Deep inheritance hierarchies increase generation complexity

Circular Dependency Prevention

Understanding Circular Dependencies

Circular dependencies occur when mock types reference each other in a way that would cause infinite recursion during code generation.

Common Scenarios:

// ❌ Direct circular reference
public class OrderService
{
    public CustomerService CustomerService { get; set; }
}

public class CustomerService  
{
    public OrderService OrderService { get; set; }
}

[Mock(typeof(OrderService))]
partial class OrderServiceMock { }      // References CustomerService

[Mock(typeof(CustomerService))]
partial class CustomerServiceMock { }   // References OrderService - CIRCULAR!

Prevention Strategies

1. Use Dependency Injection Interfaces:

// ✅ Break circular dependency with interfaces
public interface IOrderService
{
    void ProcessOrder(int orderId);
}

public interface ICustomerService
{
    void ProcessCustomer(int customerId);
}

public class OrderService : IOrderService
{
    private readonly ICustomerService _customerService;
    public OrderService(ICustomerService customerService) => _customerService = customerService;
    public void ProcessOrder(int orderId) => _customerService.ProcessCustomer(orderId);
}

[Mock(typeof(IOrderService))]
partial class OrderServiceMock { }

[Mock(typeof(ICustomerService))]
partial class CustomerServiceMock { }

2. Use Composition Over Inheritance:

// ✅ Composition pattern avoids circular references
public class ServiceContainer
{
    public IOrderService OrderService { get; }
    public ICustomerService CustomerService { get; }
    
    public ServiceContainer(IOrderService orderService, ICustomerService customerService)
    {
        OrderService = orderService;
        CustomerService = customerService;
    }
}

[Mock(typeof(ServiceContainer))]
partial class ServiceContainerMock { }

3. Redesign with Mediator Pattern:

// ✅ Mediator pattern eliminates direct dependencies
public interface IMediator
{
    Task<T> Send<T>(IRequest<T> request);
}

public class OrderService
{
    private readonly IMediator _mediator;
    public OrderService(IMediator mediator) => _mediator = mediator;
    
    public async Task ProcessOrder(int orderId)
    {
        await _mediator.Send(new ProcessCustomerRequest(orderId));
    }
}

[Mock(typeof(IMediator))]
partial class MediatorMock { }

[Mock(typeof(OrderService))]
partial class OrderServiceMock { }

Detection and Resolution

Build-Time Detection: The source generator automatically detects circular dependencies and reports MOCK004 errors. When you encounter this error:

  1. Identify the Cycle: Look at the error message to understand which types are involved
  2. Analyze Dependencies: Map out how your types reference each other
  3. Apply Patterns: Use the prevention strategies above to break the cycle
  4. Test Incrementally: Add mocks one at a time to identify where cycles form

Runtime Considerations: Even if circular dependencies don't cause build errors, they can cause runtime issues:

  • Stack overflow exceptions during mock initialization
  • Memory leaks from circular object references
  • Unpredictable behavior in dependency injection containers

Next Steps

For complete API documentation and configuration options, see API Reference. For troubleshooting common issues with advanced scenarios, see Troubleshooting.