Net4x.NSubstitute.Extensions 1.0.0.26243

dotnet add package Net4x.NSubstitute.Extensions --version 1.0.0.26243
                    
NuGet\Install-Package Net4x.NSubstitute.Extensions -Version 1.0.0.26243
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Net4x.NSubstitute.Extensions" Version="1.0.0.26243" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Net4x.NSubstitute.Extensions" Version="1.0.0.26243" />
                    
Directory.Packages.props
<PackageReference Include="Net4x.NSubstitute.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Net4x.NSubstitute.Extensions --version 1.0.0.26243
                    
#r "nuget: Net4x.NSubstitute.Extensions, 1.0.0.26243"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Net4x.NSubstitute.Extensions@1.0.0.26243
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Net4x.NSubstitute.Extensions&version=1.0.0.26243
                    
Install as a Cake Addin
#tool nuget:?package=Net4x.NSubstitute.Extensions&version=1.0.0.26243
                    
Install as a Cake Tool

Net4x.NSubstitute.Extensions

Fills the gaps between NSubstitute and Moq — strict mocks, an expression-based Setup/Verify API, MockRepository, pluggable default values, cross-substitute call order, a typed protected-member API — and adds what neither can do on its own: concrete classes, sealed classes, non-virtual members and static methods.

Nothing is wrapped. Everything hangs off a plain NSubstitute substitute, so there is no Mock<T> and no .Object to unwrap: IClock.Strict() returns an IClock you hand straight to production code, and NSubstitute's own API keeps working on it throughout.

var clock = IClock.Strict();                    // strict substitute, created from the type itself

clock.Setup(x => x.Now).Returns(new DateTime(2026, 8, 30));

Assert.Equal(2026, clock.Now.Year);
clock.Verify(x => x.Now, Times.Once());
clock.VerifyNoOtherCalls();                     // any other call would already have thrown

Install

dotnet add package Net4x.NSubstitute.Extensions

Pulls in NSubstitute 6.2.0 and Net4x.NSubstituteConcrete (Harmony), which powers the concrete and static support. Import it in test projects only: the API is exposed as C# extension members, so Setup, Verify and friends appear on every class type in scope. That is the price of the no-wrapper design.

  • Assembly: netstandard2.0, strong-named — .NET Framework 4.x, .NET Core and .NET 5+ consumers are all covered.
  • Consuming projects should compile with C# 14 (<LangVersion>latest</LangVersion> on a .NET 10 SDK): the type-first creation helpers (IClock.Strict(), Substitute.ForConcrete<T>()) are static extension members.
  • 235 tests, all green.

What it adds to NSubstitute

Provided by
Strict mocks (MockBehavior.Strict) IFoo.Strict(), sub.AsStrict(), MockBehavior
Expression-based Setup / Verify sub.Setup(…), sub.Verify(…), It, Times
MockRepository / centralized configuration MockRepository
Typed, verifiable protected members sub.Protected<T>(…), sub.VerifyProtected(…)
Pluggable default values IDefaultValueProvider, DefaultValueProviders
Call order across substitutes MockSequence
Concrete, sealed and non-virtual members Substitute.ForConcrete<T>()
Static methods Substitute.SetupStatic(…), Substitute.ForStatic(…)
Async setup sugar ReturnsAsync, ThrowsAsync

Everything NSubstitute already does — interfaces, virtual and abstract members, argument matching, sequential returns, exceptions, callbacks, events, ref/out, recursive mocks, partial mocks — keeps working unchanged, through either syntax.

Creating substitutes

var strict  = IClock.Strict();                  // throws on any call that was not set up
var loose   = IClock.Loose();                   // ordinary substitute
var partial = Account.LoosePartsOf(100m);       // partial substitute, constructor args passed through
var custom  = IClock.Mock(MockBehavior.Strict, DefaultValueProviders.Empty);

Substitute.For<IClock>().AsStrict();            // or adopt one NSubstitute already made

Setup

sub.Setup(x => x.Add(1, 2)).Returns(3);
sub.Setup(x => x.Add(It.IsAny<int>(), It.Is<int>(v => v > 10))).Returns(99);
sub.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>()))
   .Callback(call => log.Add(call.ArgAt<int>(0)))
   .Returns(c => c.ArgAt<int>(0) + c.ArgAt<int>(1));

sub.Setup(x => x.Log("boom")).Throws<InvalidOperationException>();
sub.SetupWithAnyArgs(x => x.Add(0, 0)).Returns(42);
sub.SetupSequence(x => x.Next()).Returns(1).Throws<TimeoutException>().Returns(3);

sub.SetupGet(x => x.Name).Returns("abacus");
sub.SetupSet(x => x.Name = It.IsAny<string>());
sub.SetupProperty(x => x.Name, "initial");      // behaves like a real property

sub.Setup(x => x.Child.Describe(7)).Returns("seven");            // recursive setups

sub.Setup(x => x.LoadAsync(1)).ReturnsAsync(new User(1));        // async sugar
sub.Setup(x => x.LoadAsync(2)).ThrowsAsync(new TimeoutException());

Callback and Throws go through NSubstitute's When route while Returns is configured on the router, so their order in the chain does not matter. SetupSequence repeats its final step once exhausted, matching NSubstitute's multi-value Returns(a, b, c) rather than Moq's return-to-default.

Declaring a setup is not calling: it is recorded through NSubstitute's RecordCallSpecification route, so it leaves the call record — and therefore Verify counts — untouched, and can be declared against a strict substitute.

Verification

sub.Verify(x => x.Add(1, 2));                          // at least once
sub.Verify(x => x.Add(1, 2), Times.Exactly(2));
sub.Verify(x => x.Log("x"), Times.Never(), "logging is off in this mode");
sub.VerifyWithAnyArgs(x => x.Add(0, 0), Times.Once());
sub.VerifySet(x => x.Name = "abacus", Times.Once());

sub.VerifyAll();            // every setup was used
sub.VerifyVerifiable();     // only setups marked .Verifiable()
sub.VerifyNoOtherCalls();   // nothing arrived beyond what was set up or verified

Times covers Never, Once, Exactly, AtLeast(Once), AtMost(Once) and Between with inclusive or exclusive bounds. VerifyAll and VerifyNoOtherCalls follow substitutes reached through recursive setups.

Concrete classes, sealed classes and non-virtual members

A real instance is constructed and its members are intercepted, so nothing needs to be virtual — or even overridable. Configuration and verification use exactly the same API as any other substitute.

var service = Substitute.ForConcrete<UserService>("staging");   // the real constructor runs
// var service = UserService.Concrete("staging");               // type-first spelling

service.Setup(x => x.GetUserName(1)).Returns("John Doe");

Assert.Equal("John Doe", service.GetUserName(1));
Assert.Equal("Real User Two", service.GetUserName(2));          // unconfigured: the real code runs
service.Verify(x => x.GetUserName(1), Times.Once());

var cache  = Substitute.ForConcrete<SealedCache>();             // sealed classes too
var strict = Substitute.StrictConcrete<UserService>();          // every member intercepted; unset-up calls throw

An unconfigured call runs the real implementation, so ForConcrete<T> is the concrete-class equivalent of a partial substitute rather than of Substitute.For<T>().

Static methods

Two spellings — a direct one, and a scope that unpatches on dispose:

Substitute.SetupStatic(() => FileHelper.ReadFile(It.IsAny<string>())).Returns("mock");
Assert.Equal("mock", FileHelper.ReadFile("t.txt"));
Substitute.VerifyStatic(() => FileHelper.ReadFile("t.txt"), Times.Once());

using var files = Substitute.ForStatic(typeof(FileHelper));     // or Substitute.ForStatic<FileHelper>()
files.Setup(() => FileHelper.ReadFile("t.txt")).Returns("mock");
files.VerifyNoOtherCallsOnDispose = true;

SetupStaticWithAnyArgs, SetupStaticSequence, AllowStaticCall, VerifyStaticAll and VerifyStaticNoOtherCalls round the direct form out; a StaticMock scope carries the same members unprefixed, plus ResetSetups, ResetCalls, ResetAll and VerifyAllOnDispose. A scope patches only its own type, and refuses an expression naming a static of another type.

Rules that come with runtime patching

A Harmony patch applies to a method for the whole process, not to one object, and the library is explicit about what follows from that rather than papering over it:

  • Unpatch on teardown. Call Substitute.ResetConcrete() when a fixture finishes, and disable parallelism for tests that patch (in xUnit, put them in one collection with DisableParallelization = true). A MockRepository or a StaticMock scope does its own share of this on Dispose.
  • Only members a setup names are patched, unless you ask otherwise. A call to an unpatched member is invisible, so Times.Never() and VerifyNoOtherCalls are refused — with the flag that fixes them — rather than passing for the wrong reason. Set ConcreteOptions.PatchAllMembers = true, or use MockBehavior.Strict or a default-value provider, both of which imply it.
  • ConcreteOptions.TrackCallOrder (on by default) is what lets MockSequence order concrete, static and ordinary substitutes together. Turned off, a mixed sequence is refused rather than reported from two unrelated counters.
  • Substitute.ConcreteDiagnostics reports what is currently patched, for tracking down a leak between tests.
  • sub.Arrange(…) is rejected on a concrete substitute: it exists so NSubstitute's own syntax can configure a strict proxy, and a concrete substitute is a real object, not a proxy. Use Setup(…).

MockRepository

using var mocks = new MockRepository(MockBehavior.Strict)
{
    DefaultValueProvider = DefaultValueProviders.Empty,
    VerifyAllOnDispose = true,
    VerifyNoOtherCallsOnDispose = true,
};

var clock   = mocks.Create<IClock>();
var repo    = mocks.CreatePartial<Ledger>();
var service = mocks.CreateConcrete<UserService>("staging");
var files   = mocks.CreateStatic(typeof(FileHelper));
mocks.Adopt(somethingMadeElsewhere);

VerifyAll, VerifyVerifiable and VerifyNoOtherCalls run across every substitute and report all failures at once, each labelled with the type it stands in for. Dispose runs whichever end-of-scope checks you enabled, resets the substitutes and closes the static scopes it opened.

Call order across substitutes

Received.InOrder checks a contiguous run within its own block. MockSequence checks an ordering that spans independent substitutes — concrete and static ones included — using the sequence numbers NSubstitute already stamps on every call.

MockSequence.Create()
    .Expect(connection, x => x.Open())
    .ExpectStatic(() => FileHelper.ReadFile("query.sql"))
    .Expect(command,    x => x.Execute(It.IsAny<string>()))
    .Expect(connection, x => x.Close())
    .Verify();

mocks.Sequence().Contiguous()      // repository-wide, and no other calls in between
     .Expect(log, x => x.Write("open"))
     .Expect(log, x => x.Write("close"))
     .Verify();

Unrelated calls between the expected ones are tolerated by default; Contiguous() forbids them. A verified sequence counts as verification, so a later VerifyNoOtherCalls stays quiet.

Default values

Consulted before NSubstitute's own auto-values, so they can override them.

sub.WithDefaultValues(DefaultValueProviders.Empty);        // "", empty collections, completed tasks
sub.WithDefaultValues(DefaultValueProviders.Substitutes);  // recursive mocks for classes, not just interfaces

sub.WithDefaultValues(DefaultValueProviders.Custom()
    .Register("(unset)")
    .Register<int>(call => call.GetArguments().Length)
    .RegisterMatching(t => t.IsEnum, (t, _) => Enum.GetValues(t).GetValue(0)));

DefaultValueProviders.Compose(first, second);              // first match wins

Implement IDefaultValueProvider for anything else; returning false falls through to NSubstitute. A strict substitute still throws rather than letting a provider quietly answer an unexpected call.

Protected members

var ledger = Ledger.LoosePartsOf();

ledger.Protected<int>("Calculate", It.IsAny<int>()).Returns(42);
ledger.ProtectedVoid("Audit", It.IsAny<string>()).Callback(c => seen.Add(c.ArgAt<string>(0)));
ledger.Protected<string>("get_Label").Returns("configured");   // properties via their accessor

ledger.VerifyProtected("Audit", Times.Once(), "posted");
ledger.AllowProtected("Audit", It.IsAny<string>());            // permit it on a strict substitute

A name that does not resolve raises NSubstitute's own ProtectedMethodNotFoundException; a non-virtual match raises ProtectedMethodNotVirtualException; a result type that does not fit raises UnsupportedSetupExpressionException.

Two C# limitations worth knowing

Language rules, not gaps in the library — both have a supported route around them.

1. Arg.Any<T>() cannot appear in a Setup/Verify expression. It returns ref T, and C# forbids a by-reference return inside an expression tree (CS8153). Use It.IsAny<T>() there. Plain Arg.* keeps working everywhere NSubstitute itself accepts it, including inside Arrange:

sub.Setup(x => x.Add(It.IsAny<int>(), 3)).Returns(7);          // expression API
sub.Arrange(x => x.Add(Arg.Any<int>(), 3).Returns(7));         // native API

It also covers Is, IsIn, IsNotIn, IsInRange, IsNull, IsNotNull and IsRegex.

2. ref/out arguments cannot appear in an expression tree either. Arrange those natively. Arrange suspends strict enforcement for its duration, so this works on a strict substitute too:

sub.Arrange(x => x.TryParse("42", out Arg.Any<int>())
                  .Returns(call => { call[1] = 42; return true; }));

Strict mode and existing NSubstitute code

Strictness is one custom call handler registered through NSubstitute's public ICallRouter.RegisterCustomCallHandlerFactory, reached only when nothing earlier in the replay route answered the call. So it is not a re-implementation of NSubstitute's matching, and:

  • sub.Received().Foo() and Received.InOrder use other routes and are never affected.
  • Event subscription and object's own members never require a setup, matching Moq.
  • sub.Foo(1).Returns(2) written directly against a strict substitute would trip the check, because the arranging call runs the replay route first. Wrap it in sub.Arrange(…).
  • A call configured only with native When(...).Do(...) produces no result, so a strict substitute still rejects it. Add sub.AllowCall(x => x.Foo()) to permit it.

Resetting

sub.ResetSetups();              // drop setups and return values, keep the call record
sub.ResetCalls();               // drop the call record, keep setups
sub.ResetAll();                 // both; behaviour is preserved

Substitute.ResetConcrete();     // drop every concrete and static patch — fixture teardown

Notes

  • Per-substitute state lives in a ConditionalWeakTable, so nothing here keeps a substitute alive.
  • Arrange's suspension of strictness is thread-scoped; do not await inside an Arrange lambda.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0.26243 118 8/31/2026