RSCG – SatorImaging.TDoubles
| name | SatorImaging.TDoubles |
| nuget | https://www.nuget.org/packages/SatorImaging.TDoubles/ |
| link | https://github.com/sator-imaging/TDoubles |
| author | Sator Imaging |
Generating test stubs with mocking for interfaces
This is how you can use SatorImaging.TDoubles .
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="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" />
<PackageReference Include="SatorImaging.TDoubles" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</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();
}
namespace TestClock;
[TDoubles.Mock(typeof(IMyClock))]
public partial class QuickStartRepoStub { }
[TestClass]
public class TestClock
{
[TestMethod]
public void TestMyClock()
{
var expectations = new QuickStartRepoStub();
expectations.MockOverrides.GetNow =()=>(DateTime.Now.AddYears(-1));
IMyClock mock = expectations;
var data= mock.GetNow();
Assert.AreEqual(DateTime.Now.Year -1, data.Year);
}
}
The code that is generated is
#nullable enable
namespace TestClock
{
partial class QuickStartRepoStub
: global::MockData.IMyClock
{
public QuickStartRepoStub(global::MockData.IMyClock? target = default)
{
_target = target;
MockOverrides = new MockOverrideContainer();
}
/// <summary>
/// Gets the underlying target instance being mocked.
/// </summary>
public global::MockData.IMyClock? MockTarget => _target;
private readonly global::MockData.IMyClock? _target;
/// <summary>
/// Gets the container for method and property overrides.
/// </summary>
public MockOverrideContainer MockOverrides { get; }
public global::System.DateTime GetNow()
{
OnWillMockCall("GetNow"); OnWillMockCall("GetNow", global::System.Array.Empty<object>());
global::System.DateTime __STMG_2025_08_25__ = default!;
if (MockOverrides.GetNow != null) {
__STMG_2025_08_25__ = (global::System.DateTime)MockOverrides.GetNow.Invoke();
}
return (global::System.DateTime)((MockOverrides.GetNow != null ? __STMG_2025_08_25__ : (_target != null ? ((global::MockData.IMyClock?)_target)!.GetNow() : default)));
}
public global::System.DateTime GetUtcNow()
{
OnWillMockCall("GetUtcNow"); OnWillMockCall("GetUtcNow", global::System.Array.Empty<object>());
global::System.DateTime __STMG_2025_08_25__ = default!;
if (MockOverrides.GetUtcNow != null) {
__STMG_2025_08_25__ = (global::System.DateTime)MockOverrides.GetUtcNow.Invoke();
}
return (global::System.DateTime)((MockOverrides.GetUtcNow != null ? __STMG_2025_08_25__ : (_target != null ? ((global::MockData.IMyClock?)_target)!.GetUtcNow() : default)));
}
public override bool Equals(object? obj)
{
OnWillMockCall("Equals"); OnWillMockCall("Equals", new object?[] { obj });
bool __STMG_2025_08_25__ = default!;
if (MockOverrides.Equals != null) {
__STMG_2025_08_25__ = (bool)MockOverrides.Equals.Invoke(obj);
}
return (bool)((MockOverrides.Equals != null ? __STMG_2025_08_25__ : (_target != null ? ((object?)_target)!.Equals(obj) : default)));
}
public override int GetHashCode()
{
OnWillMockCall("GetHashCode"); OnWillMockCall("GetHashCode", global::System.Array.Empty<object>());
int __STMG_2025_08_25__ = default!;
if (MockOverrides.GetHashCode != null) {
__STMG_2025_08_25__ = (int)MockOverrides.GetHashCode.Invoke();
}
return (int)((MockOverrides.GetHashCode != null ? __STMG_2025_08_25__ : (_target != null ? ((object?)_target)!.GetHashCode() : default)));
}
public override string? ToString()
{
OnWillMockCall("ToString"); OnWillMockCall("ToString", global::System.Array.Empty<object>());
string? __STMG_2025_08_25__ = default!;
if (MockOverrides.ToString != null) {
__STMG_2025_08_25__ = (string?)MockOverrides.ToString.Invoke();
}
return (string?)((MockOverrides.ToString != null ? __STMG_2025_08_25__ : (_target != null ? ((object?)_target)!.ToString() : default)));
}
/// <summary>
/// Container for method and property overrides.
/// </summary>
public sealed partial class MockOverrideContainer
{
/// <summary>
/// Override for global::MockData.IMyClock.GetNow method:<br/><c>+ DateTime GetNow()</c>
/// </summary>
public global::System.Func<global::System.DateTime>? GetNow { get; set; }
/// <summary>
/// Override for global::MockData.IMyClock.GetUtcNow method:<br/><c>+ DateTime GetUtcNow()</c>
/// </summary>
public global::System.Func<global::System.DateTime>? GetUtcNow { get; set; }
/// <summary>
/// Override for object.Equals method:<br/><c>+ bool Equals(object? obj)</c>
/// </summary>
public new global::System.Func<object?, bool>? Equals { get; set; }
/// <summary>
/// Override for object.GetHashCode method:<br/><c>+ int GetHashCode()</c>
/// </summary>
public new global::System.Func<int>? GetHashCode { get; set; }
/// <summary>
/// Override for object.ToString method:<br/><c>+ string? ToString()</c>
/// </summary>
public new global::System.Func<string?>? ToString { get; set; }
}
/// <summary>
/// The unified callback invoked before each mock call.
/// </summary>
/// <param name="memberName">The short name of original member without generic type parameters.</param>
partial void OnWillMockCall(string memberName);
/// <summary>
/// The unified callback invoked before each mock call.
/// </summary>
/// <param name="memberName">The short name of original member without generic type parameters.</param>
partial void OnWillMockCall(string memberName, object?[] args);
}
}
using System;
namespace TDoubles
{
/// <summary>
/// Attribute used to mark partial classes for mock generation.
/// The source generator will create a mock implementation that delegates to the target type.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
internal sealed class MockAttribute : Attribute
{
/// <summary>
/// Gets the target type to be mocked.
/// </summary>
public Type TargetType { get; }
/// <summary>
/// Gets or sets whether to include internal members in the mock.
/// Default is false (public members only).
/// </summary>
public bool IncludeInternals { get; set; } = false;
/// <summary>
/// Gets or sets the short names of members to exclude from the generated mock.
/// </summary>
public string[] ExcludeMemberShortNames { get; set; } = Array.Empty<string>();
/// <summary>
/// Initializes a new instance of the MockAttribute with the specified target type.
/// </summary>
/// <param name="targetType">The type to be mocked. Cannot be null.</param>
/// <param name="excludeMemberShortNames">Optional: Short names of members to exclude from the generated mock.</param>
/// <exception cref="ArgumentNullException">Thrown when targetType is null.</exception>
public MockAttribute(Type targetType, params string[] excludeMemberShortNames)
{
TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType));
ExcludeMemberShortNames = excludeMemberShortNames ?? Array.Empty<string>();
}
}
}
using System;
namespace TDoubles
{
/// <summary>
/// Exception thrown when a mock member has no override and returns a non-nullable reference type.
/// This exception provides clear information about which member lacks a mock implementation.
/// </summary>
internal class TDoublesException : Exception
{
/// <summary>
/// Gets the name of the member that lacks a mock implementation.
/// </summary>
public string MemberName { get; }
/// <summary>
/// Gets the name of the type that contains the member.
/// </summary>
public string TypeName { get; }
/// <summary>
/// Initializes a new instance of the TDoublesException with the specified member and type names.
/// </summary>
/// <param name="memberName">The name of the member that lacks a mock implementation. Cannot be null.</param>
/// <param name="typeName">The name of the type that contains the member. Cannot be null.</param>
public TDoublesException(string memberName, string typeName)
: base($"No mock override provided for member '{memberName}' in type '{typeName}' that returns a non-nullable reference type.")
{
MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName));
TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName));
}
/// <summary>
/// Initializes a new instance of the TDoublesException with the specified member name, type name, and custom message.
/// </summary>
/// <param name="memberName">The name of the member that lacks a mock implementation. Cannot be null.</param>
/// <param name="typeName">The name of the type that contains the member. Cannot be null.</param>
/// <param name="message">The custom error message.</param>
public TDoublesException(string memberName, string typeName, string message)
: base(message)
{
MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName));
TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName));
}
}
}
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles