KnockOff by Keith Voels
NuGet / site data
Details
Info
Name: KnockOff
A Roslyn Source Generator for creating unit test stubs. Unlike Moq's fluent runtime configuration, KnockOff uses partial classes for compile-time setup—trading flexibility for readability and performance.
Author: Keith Voels
NuGet: https://www.nuget.org/packages/KnockOff/
You can find more details at https://github.com/NeatooDotNet/KnockOff
Author
Keith Voels

Original Readme
KnockOff
A .NET mocking library that lets you define reusable stub classes — with full mocking capabilities built in.
Define your test double once. Reuse it across your test project. Customize it per-test with Return, Call, Verify, and When chains. No more copying mock setups between tests or maintaining shared factory methods full of Arg.Any<>().
Powered by Roslyn source generation for tighter type safety — more issues surface as compile errors instead of runtime surprises.
Claude Code was used to write this library. Skip to more AI discussion.
KnockOff Stub
There are 9 patterns total, including a standard fluent mocking approach with inline stubs. But reusable stub classes are where KnockOff stands apart:
[KnockOff]
public partial class MyRepoStub(List<User> Users) : IMyRepo
{
protected override User? GetUser_(int id)
{
return Users.Single(u => u.Id == id);
}
protected override void Update_(User user)
{
Assert.Contains(user, Users);
}
}
[KnockOff]+partial class— KnockOff generates a base class that implements every member ofIMyRepo. Your stub is a real class — define it once, reuse it across your entire test project. Pass it around, register it in DI, share it between test fixtures.- Constructor parameters —
List<User> Usersis a primary constructor. Test data flows in naturally, just like any other C# class. - Overrides are optional —
GetUser_andUpdate_override the generated defaults. Only override what you need — everything else still works with Return/Call, Return(value), or When chains. - Tighter type safety — Every Return, Call, and When call is complete in a single step — no forgotten
.Returns()that silently breaks at runtime. No manual<T1, T2>type parameters that can drift. Details →
This stub is also a full mock. It has Verify, Strict mode, Async, and Source Delegation — all on the same reusable class.
Why I Wrote KnockOff
I often wanted to reuse my mocks. Especially in my integration test library where I may even register my mocks. I found myself either copying my mock definitions code or creating shared methods like this:
NSubstitute:
public static IMyRepo NSubstituteMock(List<User> users)
{
var myRepoMock = Substitute.For<IMyRepo>();
// Setup: configure GetUser to look up from the list based on id
myRepoMock.GetUser(Arg.Any<int>())
.Returns(callInfo => users.SingleOrDefault(u => u.Id == callInfo.Arg<int>()));
// Setup: configure Update to assert user exists in list
myRepoMock.When(x => x.Update(Arg.Any<User>()))
.Do(callInfo => Assert.Contains(callInfo.Arg<User>(), users));
return myRepoMock;
}
Here's another example from PowerToys.
But I find that hard to read and unintuitive. Also, my shared methods accumulated extra parameters for variations across different tests.
So I Created KnockOff
You can create a stub to implement interfaces or non-sealed classes with virtual methods. Yet, you can still customize the stub per test. All while having the features you would expect with a full mocking library.
With the stub above, your tests are:
var myRepoKO = new MyRepoStub([new User \{ Id = 1 }, new User \{ Id = 2 }]);
var userDomainModel = new UserDomainModel(myRepoKO);
Assert.True(userDomainModel.Fetch(1));
// I have Verify on my Stub!
myRepoKO.GetUser.Verify(Called.Once);
Need different behavior for a specific test? Override with Return/Call:
var user1 = new User \{ Id = 1 }; // Ignored do to per-test configuration
var myRepoKO = new MyRepoStub([user1]);
var userDomainModel = new UserDomainModel(myRepoKO);
var user2 = new User \{ Id = 2 };
// When and Return overrides the stub methods
myRepoKO.GetUser.When(2).Return(user2).Verifiable();
myRepoKO.Update.Call(u => Assert.Same(u, user2)).Verifiable();
userDomainModel.Fetch(2);
userDomainModel.Update();
myRepoKO.Verify();
Now I have my stubs and mocks in one!
What Sets KnockOff Apart
- Reusable stub classes — Define once, customize per-test. Your stub is a real class — pass it through constructors, register it in DI.
- Source delegation — Delegate to a real implementation, override only specific methods. No equivalent in Moq or NSubstitute.
- Protected methods — Same
Return/Call/VerifyAPI, fully typed. No string-based names, no manual subclasses. - Ref/out parameters — Natural lambda syntax with
ref/outkeywords. No special matchers or index-based access. - Multiple interfaces — Unified interceptors on one stub. No
.As<T>()references or casting. - Tighter type safety — Each Return/Call/When call is complete in one step — no forgotten
.Returns()that silently breaks at runtime. - Parameter matching —
Return((a, b) => a > 0 ? 100 : 0)— standard C# conditionals instead ofArg.Is<>orIt.Is<>per parameter. - Built-in argument capture —
LastArg,LastArgs,LastSetValue,LastSetEntry— no manualArg.Do<>orCallback<>setup. - Event verification —
VerifyAdd()/VerifyRemove()/HasSubscribers— not available in Moq or NSubstitute. - Explicit Get/Set verification —
VerifyGet(Called)/VerifySet(Called)for properties and indexers. - Stubbing concrete classes — Override virtual methods on non-sealed classes with the same API.
Quick Start
######### Install
dotnet add package KnockOff
######### Create a Stub
public interface IQuickStartRepo
{
User? GetUser(int id);
}
[KnockOff]
public partial class QuickStartRepoStub : IQuickStartRepo \{ }
public class QuickStartCreateStubTests
{
[Fact]
public void CreateStub_IsReady()
{
var stub = new QuickStartRepoStub();
IQuickStartRepo repository = stub;
Assert.NotNull(repository);
}
}
######### Configure and Verify
[Fact]
public void ConfigureStub_WithReturn()
{
var stub = new QuickStartRepoStub();
stub.GetUser.Return((id) => new User \{ Id = id, Name = "Test User" });
IQuickStartRepo repository = stub;
var user = repository.GetUser(42);
Assert.NotNull(user);
Assert.Equal(42, user.Id);
Assert.Equal("Test User", user.Name);
}
[Fact]
public void VerifyCalls_WithVerifiable()
{
var stub = new QuickStartRepoStub();
stub.GetUser.Return((id) => new User \{ Id = id, Name = "Test" }).Verifiable();
IQuickStartRepo repository = stub;
var user = repository.GetUser(42);
// Verify() checks all members marked with .Verifiable()
stub.Verify();
}
The Difference
Moq:
mock.Setup(x => x.GetUser(It.Is<int>(id => id > 0)))
.Returns<int>(id => new User \{ Id = id });
NSubstitute:
var repo = Substitute.For<IUserRepo>();
repo.GetUser(Arg.Is<int>(id => id > 0)).Returns(x => new User \{ Id = x.Arg<int>() });
KnockOff:
var stub = new CompareUserRepoStub();
stub.GetUser.Return((id) => id > 0 ? new User \{ Id = id \} : null);
No It.Is<>(). No Arg.Is<>(). No x.Arg<int>(). The parameter is just id.
For side-by-side comparison tables (methods, properties, events, delegates, indexers), see the complete comparison guide.
Argument Matching
Moq:
// Moq - It.Is<T> per parameter
mock.Setup(x => x.Add(It.Is<int>(a => a > 0), It.IsAny<int>())).Returns(100);
NSubstitute:
// NSubstitute - Arg.Is<T> per parameter (permanent matchers)
calc.Add(Arg.Is<int>(a => a > 0), Arg.Any<int>()).Returns(100);
KnockOff:
// KnockOff - Returns with conditional (permanent, matches all calls)
stub.Add.Return((a, b) => a > 0 ? 100 : 0);
// KnockOff - When() for sequential matching (first match returns 100, then falls through)
stub.Add.When((a, b) => a > 0).Return(100).ThenCall((a, b) => a + b);
Multiple specific values:
Moq:
mock.Setup(x => x.Add(1, 2)).Returns(100);
mock.Setup(x => x.Add(3, 4)).Returns(200);
// Multiple specific values
calc.Add(1, 2).Returns(100);
calc.Add(3, 4).Returns(200);
stub.Add.When(1, 2).Return(100);
stub.Add.When(3, 4).Return(200);
Note: Moq and NSubstitute matchers are permanent -- they match all qualifying calls. KnockOff's When() is sequential -- matchers are consumed in order. Use Return(callback) with conditionals for permanent matching behavior.
######### Argument Capture
Moq:
// Moq - requires Callback setup
int capturedA = 0, capturedB = 0;
mock.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>()))
.Callback<int, int>((a, b) => \{ capturedA = a; capturedB = b; });
mock.Object.Add(1, 2);
NSubstitute:
// NSubstitute - requires Arg.Do in setup
int capturedA = 0, capturedB = 0;
calc.Add(Arg.Do<int>(x => capturedA = x), Arg.Do<int>(x => capturedB = x));
calc.Add(1, 2);
KnockOff:
// KnockOff - built-in, no pre-setup
var tracking = stub.Add.Return((a, b) => a + b);
ICalculator calc = stub;
calc.Add(1, 2);
var (a, b) = tracking.LastArgs; // Named tuple: a = 1, b = 2
For full comparisons of properties, events, delegates, and indexers, see the complete comparison guide.
Method Overload Resolution
The Problem: When an interface has overloaded methods with the same parameter count but different types:
public interface IFormatter
{
string Format(string input, bool uppercase);
string Format(string input, int maxLength);
}
######### Any-Value Matching
Moq:
// It.IsAny<T>() required - compiler needs the types to resolve overload
mock.Setup(x => x.Format(It.IsAny<string>(), It.IsAny<bool>())).Returns("bool overload");
mock.Setup(x => x.Format(It.IsAny<string>(), It.IsAny<int>())).Returns("int overload");
NSubstitute:
// Arg.Any<T>() required - compiler needs the types to resolve overload
formatter.Format(Arg.Any<string>(), Arg.Any<bool>()).Returns("bool overload");
formatter.Format(Arg.Any<string>(), Arg.Any<int>()).Returns("int overload");
KnockOff:
// Explicit parameter types resolve the overload - standard C# syntax
stub.Format.Return((string input, bool uppercase) => "bool overload");
stub.Format.Return((string input, int maxLength) => "int overload");
######### Specific-Value Matching
NSubstitute:
// Specific value matching - literals work when all args are specific
formatter.Format("test", true).Returns("UPPERCASE");
formatter.Format("test", 10).Returns("truncated");
KnockOff:
// Specific value matching - parameter types resolve the overload
stub.Format.When("test", true).Return("UPPERCASE");
stub.Format.When("test", 10).Return("truncated");
######### Argument Access
Moq:
// To use argument values, extract via Returns<T1, T2>:
mock.Setup(x => x.Format(It.IsAny<string>(), It.IsAny<bool>()))
.Returns<string, bool>((input, uppercase) => uppercase ? input.ToUpper() : input);
NSubstitute:
// To use argument values, extract from CallInfo:
formatter.Format(Arg.Any<string>(), Arg.Any<bool>())
.Returns(x => x.ArgAt<bool>(1) ? x.ArgAt<string>(0).ToUpper() : x.ArgAt<string>(0));
KnockOff:
// Arguments are directly available with names and types:
stub.Format.Return((string input, bool uppercase) => uppercase ? input.ToUpper() : input);
The Difference:
- Moq:
It.IsAny<bool>()+.Returns<string, bool>((input, uppercase) => ...)to match any value and access arguments - NSubstitute:
Arg.Any<bool>()+x.ArgAt<bool>(1)to match any value and access arguments - KnockOff:
(string input, bool uppercase)- standard C# lambda with named, typed parameters
Three Stub Patterns
KnockOff supports 9 patterns total. Here are the three most common:
Standalone - Reusable across your project:
[KnockOff]
public partial class ReadmeStandaloneStub : IUserRepo \{ }
Inline Interface - Test-local stubs:
[Fact]
public void InlineInterface_Pattern()
{
var stub = new Stubs.IUserRepo();
stub.GetUser.Return((id) => new User \{ Id = id });
IUserRepo repo = stub;
Assert.NotNull(repo.GetUser(1));
}
Inline Class - Stub virtual members:
[Fact]
public void InlineClass_Pattern()
{
var stub = new Stubs.MyService();
stub.GetUser.Return((id) => new User \{ Id = id });
MyService service = stub.Object;
Assert.NotNull(service.GetUser(1));
}
Roslyn Source Generation
KnockOff uses Roslyn source generation, which means:
- No more
Arg.Any<>(). No moreIt.IsAny<>(). Just write C# - If the method signature changes you get a compile error
- There's a small performance gain but honestly it's negligible
Source generation opens doors beyond traditional mocking — I've already added 9 patterns and features like Source Delegation, with more ideas to come.
What other ideas do you have? Open a discussion.
AI
This is an idea I've had for years but never took the time to implement. With my ideas and guidance, Claude Code has written the entirety of this library — the Roslyn source generator, the runtime library, the tests, and the documentation.
Source generation turned out to be a great fit for AI code generation. The work is highly patterned: analyze an interface, generate code for each member, handle edge cases across 9 patterns and 4 member types. That's exactly the kind of systematic, repetitive-but-varied work where AI excels. I designed the API and patterns; Claude Code implemented them across every combination.
######### Claude Code Skill
KnockOff includes a Claude Code skill that teaches Claude how to use the library. Copy the skills/knockoff/ directory into your project and Claude Code will know how to create stubs, configure behavior, write tests with KnockOff, and migrate from Moq — without you explaining the API.
The skill includes slash commands:
/knockoff:create-stub— Create a new stub class with the pattern of your choice/knockoff:migrate-from-moq— Convert existing Moq tests to KnockOff/knockoff:troubleshoot— Diagnose and fix common KnockOff issues
Documentation
- Getting Started - Installation and first stub
- Stub Patterns - Standalone, inline interface, inline class
- Interceptor API - Complete
Returns,Execute,Get,Setreference - Source Delegation - Delegate to real implementations
- Full Comparison Guide - Properties, events, delegates, indexers vs Moq and NSubstitute
- Migration from Moq - Step-by-step migration guide
- Migration from NSubstitute - Comparison and migration guide
License
MIT License. See LICENSE for details.
Contributing
Contributions welcome! See CONTRIBUTING.md for guidelines.
- Issues: GitHub Issues
- Pull Requests: Bug fixes, features, documentation
- Discussions: GitHub Discussions
About
Generating test stubs with mocking for interfaces
How to use
Example (source csproj, source files)
- CSharp Project
- IMyClock.cs
- TestClock.cs
This is the CSharp Project that references KnockOff
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="KnockOff" Version="0.49.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Mock\MockData.csproj" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of KnockOff in IMyClock.cs
namespace MockData;
public interface IMyClock
{
public DateTime GetNow();
public DateTime GetUtcNow();
}
This is the use of KnockOff in TestClock.cs
using KnockOff;
namespace TestClock;
[KnockOff]
public partial class QuickStartRepoStub : IMyClock \{ }
[TestClass]
public class TestClock
{
[TestMethod]
public void TestMyClock()
{
var expectations = new QuickStartRepoStub();
expectations.GetNow.Return(DateTime.Now.AddYears(-1));
IMyClock mock = expectations;
var data= mock.GetNow();
Assert.AreEqual(DateTime.Now.Year -1, data.Year);
expectations.Verify();
}
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- QuickStartRepoStub.Base.g.cs
- QuickStartRepoStub.g.cs
// <auto-generated/>
#nullable enable
namespace TestClock;
public class QuickStartRepoStubBase
{
/// <summary>Override to provide default implementation for global::MockData.IMyClock.GetNow.</summary>
protected virtual global::System.DateTime GetNow_() => default!;
/// <summary>Override to provide default implementation for global::MockData.IMyClock.GetUtcNow.</summary>
protected virtual global::System.DateTime GetUtcNow_() => default!;
}
// <auto-generated/>
#nullable enable
using System.Linq;
namespace TestClock;
partial class QuickStartRepoStub : QuickStartRepoStubBase, global::MockData.IMyClock, global::KnockOff.IKnockOffStub
{
/// <summary>Tracks and configures behavior for GetNow.</summary>
public sealed class GetNowInterceptor : global::KnockOff.Interceptors.MethodInterceptorBase<GetNowInterceptor.GetNowDelegate, global::KnockOff.Unit, global::System.DateTime>
{
/// <summary>Source object to delegate to when no callback is configured.</summary>
internal global::MockData.IMyClock? _source;
/// <summary>Delegate for GetNow.</summary>
public delegate global::System.DateTime GetNowDelegate();
public GetNowInterceptor() : base("GetNow") \{ }
protected override global::System.DateTime InvokeDelegate(GetNowDelegate del, global::KnockOff.Unit args) => del();
protected override GetNowDelegate CreateValueDelegate(global::System.DateTime value) => () => value;
protected override void RecordArgs(global::KnockOff.Unit args, MethodCallBuilderBase tracking) \{ }
protected override void RecordUnconfiguredArgs(global::KnockOff.Unit args) \{ }
/// <summary>Configures callback that repeats indefinitely. Returns builder for sequence chaining.</summary>
public MethodCallBuilderImpl Return(GetNowDelegate callback)
{
var builder = new MethodCallBuilderImpl(this);
SetupReturnCallback(callback, builder);
return builder;
}
/// <summary>Configures return value that repeats indefinitely. Returns builder for sequence chaining.</summary>
public MethodCallBuilderImpl Return(global::System.DateTime value)
{
var builder = new MethodCallBuilderImpl(this);
SetupReturnValue(value, builder);
return builder;
}
/// <summary>Configures sequence of return values. Each value returned once, last repeats.</summary>
public ReturnMethodSequenceBase Return(global::System.DateTime first, params global::System.DateTime[] rest)
{
var builder = Return(() => first);
if (rest.Length == 0)
{
return builder.ThenReturn(first);
}
var seq = builder.ThenReturn(rest[0]);
for (int i = 1; i < rest.Length; i++)
{
seq.ThenReturn(rest[i]);
}
return seq;
}
/// <summary>Invokes the configured callback. Called by explicit interface implementation.</summary>
internal global::System.DateTime Invoke(bool strict)
{
var (handled, result) = RunPriorityChain(default);
if (handled) return result;
_unconfiguredCallCount++;
RecordUnconfiguredArgs(default);
var (seqHandled, seqResult) = HandleNonVoidSequenceExhaustedRepeat(strict, default);
if (seqHandled) return seqResult;
#pragma warning disable CS8601, SYSLIB0050
if (_source is \{ \} src) return src.GetNow();
#pragma warning restore CS8601, SYSLIB0050
if (strict) throw global::KnockOff.StubException.NotConfigured("", "GetNow");
return default!;
}
/// <summary>Resets tracking state but preserves configuration and verifiable marking.</summary>
public override void Reset()
{
base.Reset();
_source = null;
}
/// <summary>Builder for callback registration. Supports tracking and lazy elevation to sequence.</summary>
public sealed class MethodCallBuilderImpl : ReturnMethodCallBuilderBase, global::KnockOff.IMethodReturnBuilder<GetNowDelegate>
{
private readonly GetNowInterceptor _typedInterceptor;
public MethodCallBuilderImpl(GetNowInterceptor interceptor) : base(interceptor)
{
_typedInterceptor = interceptor;
}
public override void Reset() => base.Reset();
/// <summary>Elevates to sequence mode and adds another callback. Returns sequence for further chaining.</summary>
public ReturnMethodSequenceBase ThenReturn(GetNowDelegate callback)
{
return ThenReturnBase(callback);
}
/// <summary>Elevates to sequence mode and adds a value. Returns sequence for further chaining.</summary>
public ReturnMethodSequenceBase ThenReturn(global::System.DateTime value) => ThenReturn(() => value);
/// <summary>Adds multiple values to the sequence. Each value returned once.</summary>
public ReturnMethodSequenceBase ThenReturn(params global::System.DateTime[] values)
{
if (values.Length == 0) \{ ElevateToSequenceBase(); return new ReturnMethodSequenceBase(_typedInterceptor, CreateNextReturnBuilder); }
var seq = ThenReturn(values[0]);
for (int i = 1; i < values.Length; i++) seq.ThenReturn(values[i]);
return seq;
}
/// <summary>Marks for verification by Stub.Verify().</summary>
public MethodCallBuilderImpl Verifiable() \{ VerifiableBase(); return this; }
/// <summary>Marks for verification by Stub.Verify() with Called constraint.</summary>
public MethodCallBuilderImpl Verifiable(global::KnockOff.Called times) \{ VerifiableBase(times); return this; }
protected override ReturnMethodCallBuilderBase CreateNextReturnBuilder() => new MethodCallBuilderImpl(_typedInterceptor);
global::KnockOff.IMethodTracking global::KnockOff.IMethodTracking.Verifiable() => Verifiable();
global::KnockOff.IMethodTracking global::KnockOff.IMethodTracking.Verifiable(global::KnockOff.Called times) => Verifiable(times);
global::KnockOff.IMethodReturnBuilder<GetNowDelegate> global::KnockOff.IMethodReturnBuilder<GetNowDelegate>.Verifiable() => Verifiable();
global::KnockOff.IMethodReturnBuilder<GetNowDelegate> global::KnockOff.IMethodReturnBuilder<GetNowDelegate>.Verifiable(global::KnockOff.Called times) => Verifiable(times);
global::KnockOff.IMethodReturnSequence<GetNowDelegate> global::KnockOff.IMethodReturnBuilder<GetNowDelegate>.ThenReturn(GetNowDelegate callback) => ThenReturn(callback);
}
}
/// <summary>Tracks and configures behavior for GetUtcNow.</summary>
public sealed class GetUtcNowInterceptor : global::KnockOff.Interceptors.MethodInterceptorBase<GetUtcNowInterceptor.GetUtcNowDelegate, global::KnockOff.Unit, global::System.DateTime>
{
/// <summary>Source object to delegate to when no callback is configured.</summary>
internal global::MockData.IMyClock? _source;
/// <summary>Delegate for GetUtcNow.</summary>
public delegate global::System.DateTime GetUtcNowDelegate();
public GetUtcNowInterceptor() : base("GetUtcNow") \{ }
protected override global::System.DateTime InvokeDelegate(GetUtcNowDelegate del, global::KnockOff.Unit args) => del();
protected override GetUtcNowDelegate CreateValueDelegate(global::System.DateTime value) => () => value;
protected override void RecordArgs(global::KnockOff.Unit args, MethodCallBuilderBase tracking) \{ }
protected override void RecordUnconfiguredArgs(global::KnockOff.Unit args) \{ }
/// <summary>Configures callback that repeats indefinitely. Returns builder for sequence chaining.</summary>
public MethodCallBuilderImpl Return(GetUtcNowDelegate callback)
{
var builder = new MethodCallBuilderImpl(this);
SetupReturnCallback(callback, builder);
return builder;
}
/// <summary>Configures return value that repeats indefinitely. Returns builder for sequence chaining.</summary>
public MethodCallBuilderImpl Return(global::System.DateTime value)
{
var builder = new MethodCallBuilderImpl(this);
SetupReturnValue(value, builder);
return builder;
}
/// <summary>Configures sequence of return values. Each value returned once, last repeats.</summary>
public ReturnMethodSequenceBase Return(global::System.DateTime first, params global::System.DateTime[] rest)
{
var builder = Return(() => first);
if (rest.Length == 0)
{
return builder.ThenReturn(first);
}
var seq = builder.ThenReturn(rest[0]);
for (int i = 1; i < rest.Length; i++)
{
seq.ThenReturn(rest[i]);
}
return seq;
}
/// <summary>Invokes the configured callback. Called by explicit interface implementation.</summary>
internal global::System.DateTime Invoke(bool strict)
{
var (handled, result) = RunPriorityChain(default);
if (handled) return result;
_unconfiguredCallCount++;
RecordUnconfiguredArgs(default);
var (seqHandled, seqResult) = HandleNonVoidSequenceExhaustedRepeat(strict, default);
if (seqHandled) return seqResult;
#pragma warning disable CS8601, SYSLIB0050
if (_source is \{ \} src) return src.GetUtcNow();
#pragma warning restore CS8601, SYSLIB0050
if (strict) throw global::KnockOff.StubException.NotConfigured("", "GetUtcNow");
return default!;
}
/// <summary>Resets tracking state but preserves configuration and verifiable marking.</summary>
public override void Reset()
{
base.Reset();
_source = null;
}
/// <summary>Builder for callback registration. Supports tracking and lazy elevation to sequence.</summary>
public sealed class MethodCallBuilderImpl : ReturnMethodCallBuilderBase, global::KnockOff.IMethodReturnBuilder<GetUtcNowDelegate>
{
private readonly GetUtcNowInterceptor _typedInterceptor;
public MethodCallBuilderImpl(GetUtcNowInterceptor interceptor) : base(interceptor)
{
_typedInterceptor = interceptor;
}
public override void Reset() => base.Reset();
/// <summary>Elevates to sequence mode and adds another callback. Returns sequence for further chaining.</summary>
public ReturnMethodSequenceBase ThenReturn(GetUtcNowDelegate callback)
{
return ThenReturnBase(callback);
}
/// <summary>Elevates to sequence mode and adds a value. Returns sequence for further chaining.</summary>
public ReturnMethodSequenceBase ThenReturn(global::System.DateTime value) => ThenReturn(() => value);
/// <summary>Adds multiple values to the sequence. Each value returned once.</summary>
public ReturnMethodSequenceBase ThenReturn(params global::System.DateTime[] values)
{
if (values.Length == 0) \{ ElevateToSequenceBase(); return new ReturnMethodSequenceBase(_typedInterceptor, CreateNextReturnBuilder); }
var seq = ThenReturn(values[0]);
for (int i = 1; i < values.Length; i++) seq.ThenReturn(values[i]);
return seq;
}
/// <summary>Marks for verification by Stub.Verify().</summary>
public MethodCallBuilderImpl Verifiable() \{ VerifiableBase(); return this; }
/// <summary>Marks for verification by Stub.Verify() with Called constraint.</summary>
public MethodCallBuilderImpl Verifiable(global::KnockOff.Called times) \{ VerifiableBase(times); return this; }
protected override ReturnMethodCallBuilderBase CreateNextReturnBuilder() => new MethodCallBuilderImpl(_typedInterceptor);
global::KnockOff.IMethodTracking global::KnockOff.IMethodTracking.Verifiable() => Verifiable();
global::KnockOff.IMethodTracking global::KnockOff.IMethodTracking.Verifiable(global::KnockOff.Called times) => Verifiable(times);
global::KnockOff.IMethodReturnBuilder<GetUtcNowDelegate> global::KnockOff.IMethodReturnBuilder<GetUtcNowDelegate>.Verifiable() => Verifiable();
global::KnockOff.IMethodReturnBuilder<GetUtcNowDelegate> global::KnockOff.IMethodReturnBuilder<GetUtcNowDelegate>.Verifiable(global::KnockOff.Called times) => Verifiable(times);
global::KnockOff.IMethodReturnSequence<GetUtcNowDelegate> global::KnockOff.IMethodReturnBuilder<GetUtcNowDelegate>.ThenReturn(GetUtcNowDelegate callback) => ThenReturn(callback);
}
}
/// <summary>Interceptor for GetNow.</summary>
public GetNowInterceptor GetNow \{ get; \} = new();
/// <summary>Interceptor for GetUtcNow.</summary>
public GetUtcNowInterceptor GetUtcNow \{ get; \} = new();
/// <summary>When true, throws StubException for unconfigured member access.</summary>
public bool Strict \{ get; set; \} = false;
/// <summary>The global::MockData.IMyClock instance. Use for passing to code expecting the interface.</summary>
public global::MockData.IMyClock Object => this;
/// <summary>Verifies all members marked with .Verifiable() were invoked as expected. Throws VerificationException with all failures if any fail.</summary>
public void Verify()
{
var failures = new global::System.Collections.Generic.List<global::KnockOff.VerificationFailure>();
if (GetNow.CheckVerification() is \{ \} getnowFailure) failures.Add(getnowFailure);
if (GetUtcNow.CheckVerification() is \{ \} getutcnowFailure) failures.Add(getutcnowFailure);
if (failures.Count > 0)
throw new global::KnockOff.VerificationException(failures);
}
/// <summary>Verifies ALL configured members were invoked at least once. Throws VerificationException with all failures if any fail.</summary>
public void VerifyAll()
{
var failures = new global::System.Collections.Generic.List<global::KnockOff.VerificationFailure>();
if (GetNow.CheckVerificationAll() is \{ \} getnowFailure) failures.Add(getnowFailure);
if (GetUtcNow.CheckVerificationAll() is \{ \} getutcnowFailure) failures.Add(getutcnowFailure);
if (failures.Count > 0)
throw new global::KnockOff.VerificationException(failures);
}
// Source(T) methods for interface delegation
/// <summary>Delegates unconfigured member access to the provided source object (global::MockData.IMyClock).</summary>
/// <param name="source">The source to delegate to, or null to clear.</param>
public void Source(global::MockData.IMyClock? source)
{
GetNow._source = source;
GetUtcNow._source = source;
}
global::System.DateTime global::MockData.IMyClock.GetNow()
{
return GetNow.Invoke(Strict);
}
global::System.DateTime global::MockData.IMyClock.GetUtcNow()
{
return GetUtcNow.Invoke(Strict);
}
}
Useful
Download Example (.NET C#)
Share KnockOff
https://ignatandrei.github.io/RSCG_Examples/v2/docs/KnockOff
Category "Tests" has the following generators:
1 Imposter
2025-12-13
2 KnockOff
2026-02-13
3 mocklis
2024-01-03
4 MockMe
2025-02-10
5 MSTest
2024-04-04
6 Ridge
2023-08-20
7 Rocks
2023-04-16
8 TUnit
2025-11-08