Skip to content

Latest commit

 

History

History
286 lines (211 loc) · 11.2 KB

SourceGenerators.md

File metadata and controls

286 lines (211 loc) · 11.2 KB

Moq.AutoMocker Source Generators

For concise signatures with overloads grouped together, see Generated extensions. See the documentation index for the core API.

Moq.AutoMock includes built-in source generators that provide compile-time code generation to enhance your testing experience. These generators automatically create boilerplate test code and extension methods, saving you time and reducing repetitive testing code.

Available Generators

Moq.AutoMock includes several source generators:

Automatically generates constructor null-check tests for your classes.

Key Features:

  • Generates tests for each nullable constructor parameter
  • Supports MSTest, xUnit, NUnit, and TUnit testing frameworks
  • Validates ArgumentNullException is thrown with correct parameter names
  • Provides customization hooks for test setup

Quick Example:

[TestClass]
[ConstructorTests(TargetType = typeof(MyController))]
public partial class MyControllerTests
{
}

Learn more →

Generates WithOptions<T>() extension method when Microsoft.Extensions.Options is referenced.

Key Features:

  • Fluent API for configuring options in tests
  • Sets up complete options infrastructure (IOptionsMonitor, IOptionsSnapshot, etc.)
  • Simplifies testing of classes that depend on IOptions<T>

Quick Example:

mocker.WithOptions<MySettings>(options => 
{
    options.Number = 42;
    options.Required = "test value";
});

Learn more →

Generates WithFakeLogging() extension method when Microsoft.Extensions.Diagnostics.Testing is referenced.

Key Features:

  • Captures log messages for verification
  • Uses Microsoft's official testing helpers
  • Enables logging behavior validation in tests

Quick Example:

mocker.WithFakeLogging();
var provider = mocker.Get<FakeLoggerProvider>();

// ... perform actions ...

var logs = provider.Collector.GetSnapshot();
Assert.IsTrue(logs.Any(log => log.Message == "Expected message"));

Learn more →

Generates WithApplicationInsights() and related extension methods when Microsoft.ApplicationInsights is referenced. Supports both 2.x and 3.x versions of Application Insights.

Key Features:

  • Application Insights 2.x: Uses a FakeTelemetryChannel with GetSentTelemetry() to capture ITelemetry items
  • Application Insights 3.x: Uses OpenTelemetry in-memory exporters with GetApplicationInsightsLogRecords(), GetApplicationInsightsMetrics(), and GetApplicationInsightsActivities()

Quick Example (3.x):

mocker.WithApplicationInsights();
var service = mocker.CreateInstance<MyService>();
service.DoWork();

var logRecords = mocker.GetApplicationInsightsLogRecords();
Assert.HasCount(2, logRecords);

Learn more →

Generates WithKeyedService<T>() extension methods when Microsoft.Extensions.DependencyInjection is referenced.

Key Features:

  • Enables testing of keyed services via IKeyedServiceProvider
  • Supports [FromKeyedServices] attribute resolution
  • Provides both eager and lazy service registration
  • Integrates seamlessly with dependency injection

Quick Example:

mocker.WithKeyedService(Mock.Of<IEmailSender>(), "primary");
mocker.WithKeyedService<ICache, RedisCache>("cache");

var service = mocker.CreateInstance<MyService>();
// Keyed services are automatically resolved

Learn more →

Generates WithMeterFactory() extension method when System.Diagnostics.DiagnosticSource 10.0+ is referenced.

Key Features:

  • Provides a real IMeterFactory implementation for metrics testing
  • Forwards Create(MeterOptions) directly to new Meter(options)
  • Tracks and disposes created meters on cleanup

Quick Example:

mocker.WithMeterFactory();
var service = mocker.CreateInstance<MetricsService>();
// IMeterFactory is automatically resolved with a working implementation

Learn more →

Generates WithFakeTimeProvider() extension method when Microsoft.Extensions.TimeProvider.Testing is referenced.

Key Features:

  • Provides a FakeTimeProvider for deterministic time-based testing
  • Registers as both TimeProvider (for injection) and FakeTimeProvider (for test control)
  • Allows advancing time with Advance() and SetUtcNow()

Quick Example:

mocker.WithFakeTimeProvider();
var fakeTime = mocker.Get<FakeTimeProvider>();
fakeTime.SetUtcNow(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));

var service = mocker.CreateInstance<MyTimeSensitiveService>();
fakeTime.Advance(TimeSpan.FromHours(1));
// Verify behavior at specific time

Learn more →

Generates WithHttpClientFactory() extension method when Microsoft.Extensions.Http is referenced.

Key Features:

  • Provides a testable IHttpClientFactory implementation
  • Caches one HttpClient per client name — the same name returns the same instance, different names return distinct instances
  • All named clients share the same testable handler used by HttpClientResolver, so existing SetupHttp*/VerifyHttp* helpers apply

Quick Example:

mocker.WithHttpClientFactory();
var factory = mocker.Get<IHttpClientFactory>();
var client = factory.CreateClient("catalog");
// IHttpClientFactory is automatically resolved with testable clients

Learn more →

Analyzer: Unnecessary Test Double Registration (AMA0003)

The informational diagnostic AMA0003 flags Use calls that register a plain, unconfigured mock or re-register a mock returned by the same AutoMocker. AutoMocker creates mocks on demand and retains the mocks returned by GetMock. Class targets, including abstract classes, are excluded even when retrieved with GetMock; the diagnostic still applies to interface targets.

// Unnecessary registrations:
mocker.Use(new Mock<IService>());
mocker.Use(new Mock<IService>().Object);
mocker.Use(mocker.GetMock<IService>());

// Create and register a fresh mock in one call instead:
var freshMock = mocker.WithMock<IService>();
freshMock.Setup(x => x.GetValue()).Returns(42);

// Or retrieve the existing mock without re-registering it:
var mock = mocker.GetMock<IService>();
mock.Setup(x => x.GetValue()).Returns(42);
// No mocker.Use(mock) is needed, even after configuration.

Direct registrations suggest WithMock<T>(), which creates and registers a fresh mock. For mocks already returned by GetMock, remove the redundant Use instead of creating a replacement.

The analyzer also recognizes parameterless Mock.Of<T>(), simple local variables, and the Use(typeof(T), instance) overload. It preserves explicitly configured mocks (including constructor arguments and object initializers). For separately created mocks stored in locals, it skips the diagnostic if the local is used elsewhere, including later setup, verification, or sharing with other code. Re-registration diagnostics require the same container and service type; property or method receivers, reassigned locals, and intervening container accesses are conservatively skipped.

Suppress the diagnostic with dotnet_diagnostic.AMA0003.severity = none in an .editorconfig.

Important: Generated Classes Are Internal Partials

Most extension classes created by these source generators are internal partial classes. The HTTP client factory extension class is public. For example, the Keyed Services generator produces:

internal static partial class AutoMockerKeyedServicesExtensions
{
    // Generated extension methods...
}

Ambiguous Reference Issues with Multiple Test Projects

This design can cause ambiguous method call errors when multiple test projects reference each other and both have the source generators enabled. Since each project generates its own internal partial class with the same name and methods, projects that share visibility (e.g., via InternalsVisibleTo or project references) may see duplicate definitions.

Example error:

The call is ambiguous between the following methods or properties:
'Moq.AutoMock.AutoMockerKeyedServicesExtensions.WithKeyedService(...)' and
'Moq.AutoMock.AutoMockerKeyedServicesExtensions.WithKeyedService(...)'

This commonly occurs when:

  • An integration test project references a unit test project
  • InternalsVisibleTo is used between test projects
  • Both projects reference Moq.AutoMock and the same packages that trigger generators (e.g., Microsoft.Extensions.DependencyInjection.Abstractions)

Solution: Disable the source generator in one of the projects (see Disabling Source Generators below).

For more details, see Issue #410.

Disabling Source Generators

Each source generator can be individually disabled using MSBuild properties in your project's .csproj file:

Generator MSBuild Property
Options Extension EnableMoqAutoMockerOptionsGenerator
Keyed Services Extension EnableMoqAutoMockerKeyedServicesGenerator
Fake Logging Extension EnableMoqAutoMockerFakeLoggingGenerator
Application Insights Extension EnableMoqAutoMockerApplicationInsightsGenerator
Meter Factory Extension EnableMoqAutoMockerMeterFactoryGenerator
Fake Time Provider Extension EnableMoqAutoMockerFakeTimeProviderGenerator
HTTP Client Factory Extension EnableMoqAutoMockerHttpClientFactoryGenerator

Example: Disabling a generator

<PropertyGroup>
  <!-- Disable the Keyed Services generator -->
  <EnableMoqAutoMockerKeyedServicesGenerator>false</EnableMoqAutoMockerKeyedServicesGenerator>
</PropertyGroup>

Example: Disabling multiple generators

<PropertyGroup>
  <EnableMoqAutoMockerKeyedServicesGenerator>false</EnableMoqAutoMockerKeyedServicesGenerator>
  <EnableMoqAutoMockerOptionsGenerator>false</EnableMoqAutoMockerOptionsGenerator>
</PropertyGroup>

Tips and Best Practices

Review Generated Code

To see the generated code, enable source generators output in your .csproj:

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>