Skip to content

Source-generated arranges with [MokkitCapture]

Many arrange verbs are pure boilerplate: start a capture, build an object from the parameters, set the capture when the chain runs. [MokkitCapture] writes that body for you. You declare a partial arrange method; the source generator (bundled in the Mokkit package) fills it in.

Mark a partial extension method with [MokkitCapture]. Its parameters describe the object to build; the out capture receives it:

public static partial class ArrangeMessages
{
[MokkitCapture]
public static partial ITestArrange StatusChanged(
this ITestArrange arrange,
out Trapture<StatusChangedMessage> message,
Guid clientId, string? name, string? email, string? phone, int status);
}

That’s the whole file — no body. The generator produces one equivalent to a hand-written Trapture.Start(out message) + arrange.Then(_ => set.Set(new StatusChangedMessage { ... })). Call it like any verb:

await Arrange
.StatusChanged(out var message, clientId, name, email, phone, (int)ClientStatus.Suspended);

The generator inspects the captured type and builds it the right way:

public sealed record Foo(int Value, string Name); // positional → constructor
public sealed record Baz { public int Code { get; init; } public string Label { get; init; } = ""; } // init props → object initializer
[MokkitCapture]
public static partial ITestArrange ArrangeFoo(this ITestArrange a, out Trapture<Foo> foo, int value, string name);
// generated: new Foo(value, name)
[MokkitCapture]
public static partial ITestArrange ArrangeBaz(this ITestArrange a, out Trapture<Baz> baz, int code, string label);
// generated: new Baz { Code = code, Label = label }
  • Positional records / constructor types → the generator calls the constructor, matching parameters by name.
  • Types with init properties → it uses an object initializer.

Either Capture<T> or Trapture<T> works as the out parameter — pick per how the value flows (see Capture vs Trapture). Unmentioned fields are simply left at their defaults — handy for testing “what happens when the contact fields are null”:

[MokkitCapture]
public static partial ITestArrange IncomingStatusChangeMissingContact(
this ITestArrange arrange, out Capture<ClientStatusChangedMessage> message, Guid clientId, int status);
// builds the message from ClientId + Status; leaves Name/Email/Phone null

Reach for [MokkitCapture] when an arrange verb is only “construct this DTO/command/message from these fields and capture it”. When the verb needs real behaviour — call an API, seed a database, set up a mock — write it by hand with .Then(...). The two mix freely in the same vocabulary file.