RSCG – KnockOff
| name | KnockOff |
| nuget | https://www.nuget.org/packages/KnockOff/ |
| link | https://github.com/NeatooDotNet/KnockOff |
| author | Keith Voels |
Generating test stubs with mocking for interfaces
This is how you can use KnockOff .
The code that you start with is
<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>
The code that you will use is
namespace MockData;
public interface IMyClock
{
public DateTime GetNow();
public DateTime GetUtcNow();
}
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();
}
}
The code that is generated is
// <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);
}
}
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/KnockOff