RSCG – KnockOff

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&#91;i&#93;);
			}
			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&#91;i&#93;);
				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&#91;i&#93;);
			}
			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&#91;i&#93;);
				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


Posted

in

, ,

by

Tags: