Provider Capabilities
This page answers a practical question: what works with each FastMoq provider today, and what remains provider-specific or unsupported.
Use it together with Provider Selection and Setup:
- that page explains how provider registration and selection works
- this page explains what each provider can actually do once selected
Why this page exists
FastMoq has two different layers of behavior:
- provider-neutral APIs such as
GetOrCreateMock(...),Verify(...),VerifyNoOtherCalls(...), andVerifyLogged(...) - count-oriented verification wrappers such as
VerifyCalledOnce(...),VerifyNotCalled(...),VerifyLoggedOnce(...), andVerifyNotLogged(...)when the assertion is exactly once or never - broader count wrappers such as
VerifyCalledExactly(...),VerifyCalledAtLeast(...),VerifyCalledAtMost(...), and the matching*AnyArgs(...)variants when the test wants provider-first counts without manualTimesSpecconstruction - method-group any-args verification through
VerifyAnyArgs(...)when the test only cares that one non-overloaded method was called and every argument can stay wildcarded - a provider-neutral diagnostics snapshot through
CreateDiagnosticsSnapshot()when the test needs to inspect tracked mocks, constructor selections, observed registrations, or captured log entries without dropping into provider-native debug surfaces - provider-specific capabilities and convenience APIs exposed by the selected provider
The selected provider determines whether features such as protected-member access, automatic property backing, base-call behavior, and logger capture are available.
Capability matrix
These values reflect IMockingProviderCapabilities in the current v4 release line.
| Capability | Moq | NSubstitute | Reflection |
|---|---|---|---|
SupportsCallBase |
Yes | No | No |
SupportsSetupAllProperties |
Yes | No | No |
SupportsProtectedMembers |
Yes | No | No |
SupportsInvocationTracking |
Yes | Yes | Yes |
SupportsLoggerCapture |
Yes | Yes | No |
What remains portable across providers
These are the safest v4 APIs to document as provider-first or provider-neutral:
GetOrCreateMock<T>()GetObject<T>()Verify(...)VerifyNoOtherCalls(...)FastArg.Any(...),FastArg.Is(...),FastArg.IsNull(...),FastArg.IsNotNull(...), andFastArg.AnyExpression<T>()inside FastMoq-supported expression flowsMocker.BuildExpression<T>()as a compatibility alias for expression-valued wildcard argumentsTimesSpecVerifyLogged(...)when the selected provider supports logger capture- scenario-builder flows that rely on provider-neutral verification
If you want migration guidance that will carry forward into v5 cleanly, start from those APIs first.
Provider-first verify rule:
- once the assertion is on
Mocks.Verify<T>(...)orMockingProviderRegistry.Default.Verify(...), useFastArg.Any(...),FastArg.Is(...),FastArg.IsNull(...),FastArg.IsNotNull(...), orFastArg.AnyExpression<T>()inside that verification expression instead ofIt.IsAny(...)orIt.Is(...)
Example:
Mocks.Verify<IOrderRepository>(
x => x.Save(FastArg.Any<Order>()),
TimesSpec.Once);
Alternatives when a Moq feature is unavailable
Some APIs are strongest in Moq today and do not have a fully equivalent provider-neutral shape.
When that happens, use this rule:
- prefer a provider-neutral FastMoq API when one exists
- otherwise prefer a fake, stub, or real test double through
AddType(...) - keep Moq only when the test fundamentally depends on behavior the other providers do not expose cleanly
| Moq-oriented feature | Alternative outside Moq | When Moq is still the right tool |
|---|---|---|
Setup(...) expression-based arrangement |
For exact-call fixed results, exact-call Task completions, exact-call callbacks, or exact-call exceptions, prefer AddMethodResult(...), AddMethodResultAsync(...), AddMethodCompletionAsync(...), AddMethodCallback(...), AddMethodCallbackAsync(...), AddMethodException(...), or AddMethodExceptionAsync(...). Otherwise use the selected provider's native arrangement style when it exists. For NSubstitute, translate Setup(...) into direct substitute calls such as substitute.Method(...).Returns(...), substitute.When(...).Do(...), Arg.Any<T>(), and Arg.Is<T>(...), or replace the collaborator with a fake/stub through AddType(...) when you want a provider-neutral path. For FastMoq-owned setup shortcuts on the Moq provider, prefer FastArg matchers such as FastArg.AnyExpression<T>() over adding new BuildExpression() usage. Keep FastMoq's verification APIs for the assert side when possible. |
When you are intentionally preserving existing Moq-shaped setup chains with minimal churn, or when the test depends on Moq-only expression setup behavior. |
VerifyLogger(...) |
Prefer VerifyLogged(...). For a first-party registration story, use AddCapturedLoggerFactory() to register callback-backed ILoggerFactory, ILogger, and ILogger<T> services directly on Mocker, or use CreateLoggerFactory() when you want to plug the same capture-backed factory into a typed IServiceProvider recipe. Use AddLoggerFactory(existingFactory) when the factory instance already exists outside FastMoq. |
When you are intentionally preserving older Moq-shaped logger assertions with minimal churn. |
Protected() for HttpMessageHandler |
Prefer WhenHttpRequest(...) or WhenHttpRequestJson(...) for HTTP behavior. |
When the test really depends on direct protected-member interception rather than request/response behavior. |
Protected() for arbitrary protected members |
Prefer testing through a public seam, extracted collaborator, or concrete fake. | When the implementation cannot reasonably be reshaped and protected-member interception is the behavior under test. |
SetupSet(...) |
For simple interface-property cases, prefer AddPropertySetterCapture<TService, TValue>(...). For broader collaborator behavior, prefer a fake or stub registered with AddType(...) that captures assigned values, usually with PropertyValueCapture<TValue>, or verify the observable downstream behavior instead of the setter interception itself. |
When the setter interception is the important behavior and introducing a helper-backed replacement or fake would create more churn than value. |
SetupAllProperties() |
For simple interface-property state, prefer AddPropertyState<TService>(...). For broader collaborator behavior or class targets, prefer a concrete fake or lightweight test double with real property state via AddType(...). |
When you specifically want mocking-library-managed property backing without creating a custom fake. |
CallBase / partial mock behavior |
Prefer a real instance or AddType(...) factory for the concrete collaborator. |
When the test intentionally relies on partial mocking rather than a real implementation or fake. |
out / ref verification with It.Ref<T>.IsAny |
Prefer wrapping the dependency behind a simpler interface, or assert on the public result / side effect instead of the raw out / ref interaction. |
When the API shape is fixed and the out / ref interaction itself is important to the test. |
These alternatives are not identical replacements. They are the practical ways to stay productive when the provider-neutral or non-Moq providers do not expose the same mocking semantics.
Moq provider
Provider package / namespace:
- package:
FastMoq.Provider.Moq - namespace for tracked-mock extensions:
FastMoq.Providers.MoqProvider
Best fit:
- lowest-churn migration from older Moq-shaped suites
- tests that need protected-member access
- tests that need automatic property backing or call-base semantics
- tests that still rely on Moq-native verification or setup patterns
Supported capability flags:
- call base
- setup all properties
- protected members
- invocation tracking
- logger capture
Provider-specific tracked-mock conveniences already exposed on IFastMock<T>:
AsMoq()Setup(...)SetupGet(...)SetupSequence(...)Protected()VerifyLogger(...)SetupLoggerCallback(...)
Practical note:
- if you need Moq-only APIs that are not wrapped as
IFastMock<T>extensions, useGetOrCreateMock<T>().AsMoq()first - keep obsolete
GetMock<T>()only as a compatibility path when preserving the old Moq shape is materially cheaper than rewriting the test
Common Moq-only pockets:
SetupSet(...)- direct
Mock<T>APIs not wrapped by FastMoq provider extensions out/refverification patterns usingIt.Ref<T>.IsAny
Recommended style:
using var providerScope = MockingProviderRegistry.Push("moq");
var dependency = Mocks.GetOrCreateMock<IOrderGateway>();
dependency.Setup(x => x.Publish("alpha"));
dependency.Instance.Publish("alpha");
Mocks.Verify<IOrderGateway>(x => x.Publish("alpha"), TimesSpec.Once);
For the common once / never cases, the shared verification surface now has explicit wrappers:
Mocks.VerifyCalledOnce<IOrderGateway>(x => x.Publish("alpha"));
Mocks.VerifyNotCalled<IOrderGateway>(x => x.Publish("beta"));
Mocks.VerifyCalledExactly<IOrderGateway>(x => x.Publish("alpha"), 2);
Mocks.VerifyLoggedOnce(LogLevel.Information, "submitted alpha");
Mocks.VerifyNotLogged(LogLevel.Error, "submission failed");
Mocks.VerifyAnyArgs<IOrderGateway, Action<string>>(gateway => gateway.Publish, TimesSpec.Once);
Mocks.VerifyCalledAtLeastAnyArgs<IOrderGateway, Action<string>>(gateway => gateway.Publish, 1);
Detached handles can use the same style without routing through MockingProviderRegistry.Default manually:
MockingProviderRegistry.VerifyCalledOnce(orderGateway, x => x.Publish("alpha"));
MockingProviderRegistry.VerifyNotCalled(orderGateway, x => x.Publish("beta"));
MockingProviderRegistry.VerifyAnyArgs<IOrderGateway, Action<string>>(orderGateway, gateway => gateway.Publish, TimesSpec.Once);
MockingProviderRegistry.VerifyNoOtherCalls(orderGateway);
For exact-call fixed results that do not need a broader provider-native setup chain, prefer the shared helper surface first:
var gateway = Mocks.AddMethodResult<IOrderGateway, Order?>(
x => x.Load("order-42"),
expectedOrder);
gateway.Load("order-42").Should().BeSameAs(expectedOrder);
Mocks.Verify<IOrderGateway>(x => x.Load("order-42"), TimesSpec.Once);
Use AddMethodResultAsync(...) for the same shape when the collaborator returns Task<T>, AddMethodCompletionAsync(...) when it returns Task, AddMethodCallback(...) / AddMethodCallbackAsync(...) when the exact-call behavior is a simple side effect, and AddMethodException(...) / AddMethodExceptionAsync(...) when the exact-call behavior is a fixed exception rather than a value.
Shared setup helper boundary
Use the shared setup helpers when the arranged behavior is narrow enough that FastMoq can own it directly.
| If the arranged behavior is... | Prefer... | Keyed-service note | Fall back when... |
|---|---|---|---|
one exact-call fixed return, fixed Task<T> result, completed Task, callback, or exception on an interface collaborator |
AddMethodResult(...), AddMethodResultAsync(...), AddMethodCompletionAsync(...), AddMethodCallback(...), AddMethodCallbackAsync(...), AddMethodException(...), or AddMethodExceptionAsync(...) |
these helpers currently wrap the currently resolved service and do not expose keyed-specific overloads; when the collaborator role is keyed, keep the keyed separation first and then use a keyed tracked mock or keyed fake for that role | the arrangement needs sequencing, conditional behavior, advanced callbacks, or a class target |
| simple readable and writable property state on an interface collaborator | AddPropertyState<TService>(...) |
AddPropertyState(...) currently wraps the currently resolved service and does not expose keyed-specific overloads; for keyed roles, prefer keyed mocks or keyed fixed instances first |
the target is a class, the state model is broader than simple property backing, or the keyed role needs its own explicit fake |
| capturing assignments to one interface property | AddPropertySetterCapture<TService, TValue>(...) |
AddPropertySetterCapture(...) currently wraps the currently resolved service and does not expose keyed-specific overloads; for keyed roles, prefer keyed mocks or keyed fixed instances first |
setter interception is not the real behavior under test, or the target is a class |
| one fixed keyed dependency instance | AddKeyedType(...) plus GetKeyedObject<T>(...) when needed |
keyed registrations are first-class here | the dependency is still conceptually a mock that you want FastMoq to track |
Practical rule:
- shared setup helpers are the first choice for unkeyed interface collaborators when the behavior is exact-call or simple property state
- keyed DI contracts should preserve the key boundary first; if the helper shape still matters after that, use a keyed tracked mock or keyed fake instead of expecting the unkeyed helper overloads to retarget themselves
- when the arrangement needs more than the table above, stay honest and use provider-native setup or an explicit fake/stub
Shared verification boundary
The shared verification surface is intentionally broader than the shared setup surface, but it still has explicit stop points.
| If the assertion is... | Prefer... | ScenarioBuilder note | Stay provider-native when... |
|---|---|---|---|
| exact-call verification with explicit argument intent | Verify(...) with TimesSpec and FastArg markers where needed |
.Verify<T>(...) is the inline ScenarioBuilder path |
the test fundamentally depends on provider-only verification semantics |
| once / never / exact count / at-least / at-most count | VerifyCalledOnce(...), VerifyNotCalled(...), VerifyCalledExactly(...), VerifyCalledAtLeast(...), or VerifyCalledAtMost(...) |
ScenarioBuilder does not currently add separate count-wrapper methods; use .Verify<T>(..., TimesSpec...) inside the scenario or call the wrappers on Mocks / MockingProviderRegistry around execution |
the assertion is really about call order, sequences, or another provider-specific concept |
| any-args verification for one non-overloaded member | VerifyAnyArgs(...) and the matching *AnyArgs(...) count helpers |
use the normal .Verify<T>(...) path inside ScenarioBuilder when you need inline verification; use VerifyAnyArgs(...) around execution when the scenario only needs wildcard matching |
the member shape is provider-specific or the test needs a provider-native sequence / event assertion |
| no-other-calls on a tracked or detached handle | VerifyNoOtherCalls(...) |
.VerifyNoOtherCalls<T>() is supported inline on ScenarioBuilder |
the suite intentionally preserves a provider-native no-other-calls surface |
| captured logger assertions | VerifyLogged(...), VerifyLoggedOnce(...), and VerifyNotLogged(...) when the provider supports logger capture |
call the logger verification on Mocks around scenario execution; ScenarioBuilder does not add a separate logger-verification wrapper |
the selected provider does not support logger capture |
| call order, sequences, events, protected members, or other provider-bound semantics | the selected provider's native verification APIs | do not expect ScenarioBuilder to flatten these into a shared abstraction | the provider-specific feature is the thing under test |
When you need Moq-native behavior that is not exposed as a tracked shortcut, step through AsMoq():
Mocks.GetOrCreateMock<IOrderGateway>()
.AsMoq()
.SetupSet(x => x.Mode = It.IsAny<string>());
For simple SetupSet(...) cases on interface properties, the preferred first-party answer is AddPropertySetterCapture<TService, TValue>(...):
var modeCapture = Mocks.AddPropertySetterCapture<IOrderGateway, string?>(x => x.Mode);
CreateComponent();
Component.Run();
modeCapture.Value.Should().Be("fast");
When the component under test comes from MockerTestBase<TComponent>, call CreateComponent() after adding the capture unless you registered it in the test base setup path before component creation.
For simple SetupAllProperties() cases on interface collaborators, the preferred first-party answer is AddPropertyState<TService>(...):
var channel = Mocks.AddPropertyState<IOrderSubmissionChannel>();
CreateComponent();
await Component.SubmitAsync("order-42", expedited: true, CancellationToken.None);
channel.Mode.Should().Be("fast");
That keeps the important part of the test explicit: the collaborator needs real property state, not Moq-specific property plumbing.
AddPropertyState<TService>(...) keeps its original write-through behavior by default. If the test needs detached property state on the proxy registration without mutating the previously wrapped instance, use PropertyStateMode.ProxyOnly:
var channel = Mocks.AddPropertyState<IOrderSubmissionChannel>(PropertyStateMode.ProxyOnly);
CreateComponent();
await Component.SubmitAsync("order-42", expedited: true, CancellationToken.None);
channel.Mode.Should().Be("fast");
If the collaborator needs more behavior than one captured property, or the target is not an interface, fall back to a fake plus PropertyValueCapture<TValue>:
var modeCapture = new PropertyValueCapture<string?>();
Mocks.AddType<IOrderGateway>(_ => new OrderGatewayStub(modeCapture));
Component.Run();
modeCapture.Value.Should().Be("fast");
sealed class OrderGatewayStub(PropertyValueCapture<string?> capture) : IOrderGateway
{
public string? Mode
{
get => capture.Value;
set => capture.Record(value);
}
}
That combination keeps the test portable across providers, makes the arranged state explicit, and avoids tying the test to Moq-only setter interception when the important behavior is the assigned value.
Repo-backed references:
FastMoq.Tests/MoqProviderExtensionTests.csFastMoq.Tests/ProviderTests.cs
NSubstitute provider
Provider package / namespace:
- package:
FastMoq.Provider.NSubstitute - namespace for tracked-mock extensions:
FastMoq.Providers.NSubstituteProvider
Best fit:
- suites intentionally written against NSubstitute behavior
- teams that want provider-neutral verification with NSubstitute-backed arrangement code
Supported capability flags:
- invocation tracking
- logger capture
Not supported by the provider capabilities:
- call base
- setup all properties
- protected members
Practical note:
- prefer portable FastMoq verification APIs after arranging substitute behavior
- if a test fundamentally depends on protected members or auto-backed property semantics, NSubstitute is not the right provider for that test shape today
Recommended style:
using var providerScope = MockingProviderRegistry.Push("nsubstitute");
var dependency = Mocks.GetOrCreateMock<IOrderGateway>();
dependency.AsNSubstitute().GetValue().Returns("configured");
dependency.Instance.Publish("alpha");
dependency.Received(1).Publish("alpha");
Mocks.Verify<IOrderGateway>(x => x.Publish("alpha"), TimesSpec.Once);
Quick Moq-to-NSubstitute translation rules:
Setup(x => x.Method()).Returns(value)becomesAsNSubstitute().Method().Returns(value)Setup(x => x.Method(It.IsAny<T>()))becomesAsNSubstitute().Method(Arg.Any<T>())Setup(x => x.Method(It.Is<T>(predicate)))becomesAsNSubstitute().Method(Arg.Is<T>(predicate))Setup(x => x.VoidMethod()).Callback(...)becomesAsNSubstitute().When(x => x.VoidMethod()).Do(...)SetupSequence(...)becomesReturns(value1, value2, ...)SetupGet(...)becomes a direct propertyReturns(...)
If a migrated test still needs Protected() or CallBase, that test should usually stay on Moq or move to a fake rather than trying to force an NSubstitute equivalent. For simple interface-property cases, prefer AddPropertySetterCapture<TService, TValue>(...) or AddPropertyState<TService>(...) before keeping SetupSet(...) or SetupAllProperties() purely for habit. PropertyValueCapture<TValue> remains the default FastMoq answer when the test only needs to observe property assignments rather than exercise Moq itself.
Repo-backed references:
FastMoq.Tests/NSubstituteProviderExtensionTests.csFastMoq.Tests/ProviderTests.cs
Reflection provider
Provider package / namespace:
- built into
FastMoq.Core - namespace:
FastMoq.Providers.ReflectionProvider
Best fit:
- dependency-light provider-neutral baseline
- suites that want FastMoq without bringing in an external mocking library
- tests that only need interface interception plus provider-neutral verification
Supported capability flags:
- invocation tracking
Not supported by the provider capabilities:
- call base
- setup all properties
- protected members
- logger capture
Important implementation constraints:
- interface interception is supported through
DispatchProxy - non-interface types fall back to public parameterless construction and do not get full interception behavior
- reflection is the default provider when you do nothing
Practical note:
- this is a baseline provider, not a drop-in replacement for full Moq semantics
- verification is best-effort: only direct method-call expressions are supported, direct constant arguments are compared with
Equals(...), and richer matcher or predicate semantics are not interpreted - if your migrated tests rely on
GetMock<T>(), directMock<T>access,Protected(), orVerifyLogger(...), they should not stay on reflection
If argument intent matters beyond direct constant equality, prefer a provider that exposes richer matcher behavior or assert on the observable result instead of treating reflection verification as equivalent to Moq or NSubstitute matcher semantics.
Recommended style:
using var providerScope = MockingProviderRegistry.Push("reflection");
var dependency = Mocks.GetOrCreateMock<IOrderGateway>();
dependency.Instance.Publish("alpha");
Mocks.Verify<IOrderGateway>(x => x.Publish("alpha"), TimesSpec.Once);
Mocks.VerifyNoOtherCalls<IOrderGateway>();
Repo-backed references:
FastMoq.Tests/ProviderTests.cs
Custom providers
Custom providers should document the same things this page documents for the built-in ones:
- capability flags from
IMockingProviderCapabilities - supported tracked-mock convenience APIs, if any
- known unsupported behaviors
- migration caveats for provider-specific patterns
Important extension-model note:
- the built-in providers are not inheritance extension points
- if you want custom behavior, implement a new
IMockingProvider - when the change is incremental rather than a full rewrite, prefer a wrapper or decorator provider that delegates to an existing provider and adjusts only the behavior you need
- provider-authored tracked helpers can opt into additional FastMoq-owned behavior by implementing optional extension interfaces such as
ITrackedMockPropertyConfiguratoron the provider and exposing the creating provider throughIProviderBoundFastMockon the wrapper
If your team writes its own provider, treat this matrix format as the minimum documentation bar.
Recommended documentation pattern
For future provider docs, keep the structure consistent:
- What the provider is for.
- Which
IMockingProviderCapabilitiesflags are true or false. - Which provider-specific helpers or namespaces are required.
- Which common test shapes still need a different provider.
That keeps migration guidance, provider selection, and provider-specific limitations aligned instead of scattering the rules across multiple pages.