FastMoq
Search Results for

    Framework And Web Helper Migration

    This page collects the migration guidance that tends to live in shared helpers, framework-specific test plumbing, and FastMoq.Web setup rather than in individual test methods.

    Open this page when the highest-churn migration work is in base classes, controller helpers, principals, HttpContext, keyed DI, or service-provider shims.

    If the migration churn is specifically in Blazor component tests, MockerBlazorTestBase<T>, RenderParameters, nested component targeting, or bUnit package fallout, use bUnit and Blazor test migration first and come back here for the broader web-helper and framework-helper cleanup.

    Shared test helpers first

    Before rewriting leaf tests, inspect shared base classes, helper wrappers, and test utilities first.

    In mature suites, the highest-leverage migration changes often live there rather than in individual test methods.

    Start with helpers that still centralize any of these patterns:

    • .Object access on raw Mock<T> values
    • .Reset() calls on provider-specific mocks
    • Func<Times> or raw Times helper signatures
    • test-framework output helpers passed through shared constructor-check or diagnostic helper wrappers
    • local wrappers around principals, controller contexts, or request setup
    • framework service-provider shims such as InstanceServices, IServiceProvider, or similar test bootstrap plumbing

    One deliberate helper migration can remove the same churn from dozens of files.

    Keyed services: keep separate doubles when selection matters

    If the constructor under test takes the same interface more than once and those parameters are distinguished by DI service keys, one unkeyed GetMock<T>(), GetOrCreateMock<T>(), or AddType<T>() can make the migration look correct while hiding a real behavior difference.

    The suite passes, but it can no longer catch a public or private or primary or secondary swap because both production dependencies were collapsed into one double.

    Migration rule:

    • keep ordinary unit tests ordinary
    • but if dependency selection is part of the behavior, use two distinct doubles
    • in FastMoq, the preferred keyed options are GetOrCreateMock<T>(new MockRequestOptions { ServiceKey = ... }), AddKeyedType(...), and GetKeyedObject<T>()
    • explicit constructor injection with two separate doubles is equally valid when that is clearer
    • add one small wiring-focused test only if you also want coverage that the keyed DI contract itself is honored

    For the fuller keyed example, see Testing Guide: Keyed Services And Same-Type Dependencies.

    Azure Functions worker: typed InstanceServices

    Azure Functions worker tests deserve one explicit warning: FunctionContext.InstanceServices should behave like a typed IServiceProvider, not like a shim that returns the same object for every requested service type.

    If the suite uses Azure Functions worker helpers, prefer the built-in typed helper path:

    using FastMoq.AzureFunctions.Extensions;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Extensions.DependencyInjection;
    
    Mocks.AddFunctionContextInstanceServices(services =>
    {
        services.AddSingleton(new WidgetClock());
    });
    
    var context = Mocks.GetObject<FunctionContext>();
    var clock = context.InstanceServices.GetRequiredService<WidgetClock>();
    

    Package note:

    • if the test project references the aggregate FastMoq package, these Azure Functions helpers are already included
    • if the test project references FastMoq.Core directly, add FastMoq.AzureFunctions and import FastMoq.AzureFunctions.Extensions before using CreateFunctionContextInstanceServices(...) or AddFunctionContextInstanceServices(...)
    • the generic typed IServiceProvider helpers stay in FastMoq.Core

    Analyzer note:

    • FMOQ0013 warns on direct FunctionContext.InstanceServices mocking more broadly across Setup(...), SetupGet(...), and SetupProperty(...) so those shims move toward the typed helper path
    • the built-in code fix is narrower: it only appears when FastMoq.AzureFunctions is already referenced, and it rewrites the safe tracked-provider cases Setup(x => x.InstanceServices).Returns(provider), SetupGet(x => x.InstanceServices).Returns(provider), and SetupProperty(x => x.InstanceServices, provider) to context.AddFunctionContextInstanceServices(provider) and adds using FastMoq.AzureFunctions.Extensions; when needed
    • SetupSet(...) is not part of this analyzer. When the real need is provider-neutral setter observation rather than Moq setter interception, prefer a fake or stub with PropertyValueCapture<TValue>
    • broader IServiceProvider shim warnings still stay warning-only when the right replacement depends on the suite's real service graph, but the built-in FMOQ0013 fix now also handles the direct tracked-shim cases GetOrCreateMock<IServiceProvider>(), GetOrCreateMock<IServiceScopeFactory>(), GetOrCreateMock<IServiceScope>(), manual scope-factory extraction such as AddType<IServiceScopeFactory>(provider.GetRequiredService<IServiceScopeFactory>()), direct IServiceScope.ServiceProvider setup, and the paired CreateScope() pattern when the returned tracked scope also exposes a concrete provider in the same helper block

    If a suite already has a local Azure helper wrapper, re-point that wrapper to CreateFunctionContextInstanceServices(...) or AddFunctionContextInstanceServices(...) first and keep the existing call sites stable until the suite is green.

    Avoid helpers that return one object such as ILoggerFactory for every GetService(Type) request. Those shims can let the suite keep running while hiding cast failures for typed services such as IOptions<WorkerOptions>.

    Typed IServiceProvider helpers

    The same rule applies outside Azure Functions.

    If a base class or helper still does this:

    var provider = Mocks.GetOrCreateMock<IServiceProvider>();
    provider.Setup(x => x.GetService(It.IsAny<Type>())).Returns(someSharedObject);
    

    replace it with a typed provider shape:

    Mocks.AddServiceProvider(services =>
    {
        services.AddLogging();
        services.AddOptions();
        services.AddSingleton(new WidgetClock());
    });
    

    That migration is usually the highest-leverage cleanup in suites that centralize framework bootstrap inside a shared helper. One helper rewrite removes a noisy anti-pattern from many tests at once.

    Analyzer note:

    • FMOQ0013 warns on direct FastMoq IServiceProvider, IServiceScopeFactory, and IServiceScope shim setup, plus manual scope-factory extraction, so those helpers move toward CreateTypedServiceProvider(...), CreateTypedServiceScope(...), AddServiceProvider(...), or AddServiceScope(...).
    • the built-in fix covers the direct tracked-provider and tracked-scope cases, manual scope-factory extraction, direct IServiceScope.ServiceProvider setup, and the paired CreateScope() case when FMOQ0013 can recover the provider expression from the same helper block. It adds using FastMoq.Extensions; when needed. More entangled shim setups still need manual migration.

    If the same helper layer also hand-rolls IOptions<T> setup, move that boilerplate at the same time:

    Mocks.SetupOptions(new WorkerClientOptions
    {
        RetryCount = 3,
        Endpoint = "https://fastmoq.dev"
    });
    

    Keep AddType(instance) for non-options real instances such as MemoryCache, clocks, or fixed framework objects. Use SetupOptions(...) only for the repeated IOptions<T> wrapper shape.

    Temporary compatibility cleanup in shared helpers

    When you are already touching shared framework helpers, treat these as high-priority cleanup targets:

    • GetMock<IServiceProvider>() or GetOrCreateMock<IServiceProvider>() used to emulate typed DI
    • context-aware compatibility AddType(...) overloads that are really framework-type customizations
    • local FunctionContext.InstanceServices wrappers that do not build a real provider

    Those patterns are still supported to keep v4 migrations moving, but they are the exact spots where a small helper rewrite gives the biggest long-term payback.

    Test-framework output helpers: keep the adapter local

    If a shared helper still forwards a test-framework output object directly into FastMoq constructor-check helpers, keep the framework adapter local and pass a neutral line-writer callback into FastMoq instead.

    Before:

    action.EnsureNullCheckThrown(parameterName, constructorName, output);
    

    After:

    action.EnsureNullCheckThrown(parameterName, constructorName, output.WriteLine);
    

    Why this matters:

    • this FastMoq path is now framework-neutral
    • the helper migration is usually one local signature fix rather than a rewrite of every leaf test
    • the same shape works with xUnit, NUnit, MSTest, or an in-memory list collector because FastMoq only needs Action<string>

    Recommended helper shape:

    • keep the test-framework output type in the test project if that project wants it
    • adapt it to Action<string> at the helper boundary before calling FastMoq
    • if the helper only used output for occasional diagnostics, prefer dropping the callback entirely instead of preserving framework-specific plumbing forever

    Shared logger helper wrappers: use first-party registrations

    If a shared helper only exists to register ILoggerFactory, ILogger, or ILogger<T>, replace that wrapper with the first-party logging helpers instead of preserving a private adapter layer.

    Direct Mocker registration:

    Mocks.AddLoggerFactory();
    
    var logger = Mocks.GetObject<ILogger<WidgetProcessor>>();
    logger.LogInformation("processing complete");
    
    Mocks.VerifyLogged(LogLevel.Information, "processing complete");
    

    Typed provider composition:

    var loggerFactory = Mocks.CreateLoggerFactory();
    
    Mocks.AddServiceProvider(services =>
    {
        services.AddSingleton(loggerFactory);
        services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
    });
    

    If the suite wants logger output mirrored to a line writer or other sink after Mocker already exists, use the sink-aware overloads instead of preserving a private logger wrapper:

    Mocks.AddLoggerFactory(output.WriteLine, replace: true);
    
    var loggerFactory = Mocks.CreateLoggerFactory((logLevel, eventId, message, exception) =>
    {
        output.WriteLine($"[{logLevel}] {message}");
    });
    

    Keep any test-framework-specific output adapter local to the test project. The adapter should stop at Action<string> or the raw logging callback overload rather than pushing a framework dependency through shared FastMoq helpers. If the suite truly needs extra providers, continue to plug them into CreateLoggerFactory(...) through the logging-builder callback.

    Analyzer note:

    • FMOQ0003 prefers VerifyLogged(...) over legacy VerifyLogger(...) when the assertion can stay provider-safe.
    • FMOQ0019 prefers SetupOptions(...) over repeated manual IOptions<T> setup.
    • FMOQ0030 prefers AddLoggerFactory(...) over direct AddType<ILoggerFactory>(new ...output-helper...), AddType<ILogger>(new ...output-helper...), and AddType<ILogger<T>>(new ...output-helper...) registrations when the logger registration is just mirroring logs into an xUnit-style output helper. The diagnostic is advisory only because repo-local logger wrappers can carry extra formatting, filtering, or scope behavior that FastMoq cannot prove is safe to rewrite automatically.
    • FMOQ0036 prefers SetupLoggerCallback(...) over tracked GetOrCreateMock<ILogger...>().Setup(x => x.Log(...)).Callback(...) chains when the callback only reads normalized level, event id, message text, or exception output. It intentionally does not rewrite callbacks that inspect raw structured logger state.

    Web test helpers

    For controller tests, request-driven tests, and IHttpContextAccessor-driven tests, prefer the FastMoq.Web helpers instead of continuing to hand-roll local request and principal setup.

    For Blazor component tests, prefer the dedicated bUnit and Blazor test migration guide. This page stays focused on HTTP, controller, principal, and general framework-helper migration rather than component-rendering API changes.

    Package note:

    • if the test project references the aggregate FastMoq package, these helpers are already available
    • if the test project references FastMoq.Core directly, add FastMoq.Web before migrating local controller, principal, or HttpContext helpers to the built-in web helper surface
    • for the full package-choice overview, see Getting Started package choices

    The main helpers to look for during migration are:

    • CreateHttpContext(...)
    • CreateControllerContext(...)
    • SetupClaimsPrincipal(...)
    • AddHttpContext(...)
    • AddHttpContextAccessor(...)

    These helpers usually replace a large amount of repetitive local test setup. In many migrated suites, existing repo-local helpers become thin wrappers over FastMoq.Web first, and only later get simplified or removed.

    Principal decision table

    Test shape Preferred helper Notes
    Role-only test user SetupClaimsPrincipal(params roles) or CreateControllerContext(params roles) Best default when the test only cares about authenticated roles and the compatibility defaults are acceptable.
    Explicit custom claims SetupClaimsPrincipal(claims, options) Use IncludeDefaultIdentityClaims = false when exact claim preservation matters more than compatibility backfilling.
    Controller test reading ControllerContext.HttpContext.User CreateControllerContext(...) Prefer assigning ControllerContext directly instead of creating a principal separately and wiring HttpContext.User by hand.

    Important custom-claims note

    Custom-claim scenarios can behave differently from role-only helpers.

    FastMoq.Web adds compatibility defaults for identity-style claims unless told not to. That means role-only or convenience overloads are useful for common tests, but exact-claim tests should be explicit about the options they want.

    If a test must preserve the exact incoming claims for ClaimTypes.Name, ClaimTypes.Email, or related identity values, disable compatibility backfilling:

    var principal = Mocks.SetupClaimsPrincipal(
        claims,
        new TestClaimsPrincipalOptions
        {
            IncludeDefaultIdentityClaims = false,
        });
    

    Preferred v4 MVC controller pattern

    When the controller reads from ControllerContext.HttpContext.User, prefer assigning the controller context during component creation.

    using FastMoq.Web.Extensions;
    
    public class OrdersControllerTests : MockerTestBase<OrdersController>
    {
        private HttpContext requestContext = default!;
    
        protected override Action<Mocker>? SetupMocksAction => mocker =>
        {
            requestContext = mocker
                .CreateHttpContext("Admin")
                .SetRequestHeader("X-Correlation-Id", "corr-123")
                .SetQueryParameter("includeInactive", "true");
    
            mocker.AddHttpContextAccessor(requestContext);
            mocker.AddHttpContext(requestContext);
        };
    
        protected override Action<OrdersController> CreatedComponentAction => controller =>
        {
            controller.ControllerContext = Mocks.CreateControllerContext(requestContext);
        };
    }
    

    That is the preferred v4 migration target for older controller helpers that manually created a DefaultHttpContext, assigned User, and then copied that state into both DI and ControllerContext.

    Preferred custom-claims pattern

    When the test depends on specific ClaimTypes.Name, email, or other identity semantics, build the principal explicitly and keep compatibility defaults off when exact preservation matters.

    using System.Security.Claims;
    using FastMoq.Web;
    using FastMoq.Web.Extensions;
    
    public class OrdersControllerClaimTests : MockerTestBase<OrdersController>
    {
        protected override Action<OrdersController> CreatedComponentAction => controller =>
        {
            var claims = new[]
            {
                new Claim(ClaimTypes.Name, "Adele Vance"),
                new Claim(ClaimTypes.Email, "adele.vance@microsoft.com"),
                new Claim(ClaimTypes.Role, "Admin"),
            };
    
            controller.ControllerContext = Mocks.CreateControllerContext(
                claims,
                new TestClaimsPrincipalOptions
                {
                    IncludeDefaultIdentityClaims = false,
                });
        };
    }
    

    This avoids the common migration mistake of assuming the role-only overloads and the custom-claims overloads behave identically.

    Migrating local web helpers

    If a suite already has local SetupClaimsPrincipal(...), CreateControllerContext(...), or similar helpers, the recommended migration path is:

    1. Re-point the local wrapper to FastMoq.Web first.
    2. Keep existing call sites stable until the suite is green.
    3. Simplify or remove the wrapper later only if the direct FastMoq call sites are actually clearer.

    That approach keeps the migration low-risk. After v4 migration, those local helpers often become thin wrappers rather than remaining hand-rolled infrastructure.

    If the suite already exposes a local CreateHttpRequest(...) helper, use the same rule. Prefer building the underlying request with CreateHttpContext(...), SetRequestHeader(...), SetRequestBody(...), and SetRequestJsonBody(...), then return httpContext.Request from the local wrapper if that is still the clearest call-site shape.

    Keep only the repo-specific parts local. For example, if the app stamps custom encoded principal headers, tenant headers, or other product-specific request metadata, keep that serialization in the local helper but stop hand-rolling the DefaultHttpContext, header collection, and request body wiring.

    using FastMoq.Web.Extensions;
    
    private static HttpRequest CreateHttpRequest(
        Mocker mocks,
        IServiceProvider? services = null,
        string? requestBody = null,
        ClaimsPrincipal? principal = null)
    {
        var httpContext = principal is null
            ? mocks.CreateHttpContext(static _ => { })
            : mocks.CreateHttpContext(principal, static _ => { });
    
        if (services is not null)
        {
            httpContext.RequestServices = services;
        }
    
        if (requestBody is not null)
        {
            httpContext.SetRequestBody(requestBody, contentType: "application/json");
        }
    
        // Keep app-specific request headers local to the suite.
        return httpContext.Request;
    }
    

    That keeps the common ASP.NET Core request mechanics on the FastMoq side while still leaving room for suite-specific headers or claim serialization that FastMoq should not hard-code.

    In this article
    Back to top
    Generated 2026-04-24 22:58 UTC