Testing & Reliable Design · Lesson 10

Fakes that record relevant effects

Verify a meaningful notification contract without sending a message.

Define the observable contract

Some contracts include an externally meaningful effect rather than only a returned value. A confirmation may need to emit one notice after a valid code is accepted and none when input is rejected. A recording fake can expose that interaction without sending a real message. The important choice is which interaction belongs to the public behavior. Count a meaningful boundary effect because the requirement needs it, not every internal helper call merely because instrumentation can observe it. This lesson's sink is an in-memory list, with no email, network delivery, or service integration hidden behind its Send method.

Walk through the real program

Confirmation.Complete receives A1, validates that the code is nonblank, and sends the string confirmed:A1 to RecordingSink. The first two outputs are calls:1 and that recorded message. Completing with an empty code throws ArgumentException before calling the sink; after the deliberate handler, the final output is calls-after-rejection:1. The list has not acquired a second notice. These observations check content and absence of an invalid-input effect. They do not prove an external recipient received anything or that a remote delivery system deduplicates retries.

Why the design is testable

A useful fake records enough context to distinguish the required effect. A count alone would not reveal a notice with the wrong code, while content alone might overlook duplicate sends. The sample therefore inspects both. For multiple effects, define whether ordering matters and record only the sequence the boundary contract requires. Keep the fake deterministic and small. If it begins reproducing the whole real service, it can accumulate its own defects and give tests confidence in an invented system rather than in the integration they eventually need to exercise.

A boundary that matters

An exception after an effect has occurred can create a partial operation. This implementation validates before sending, so its blank-code rejection leaves the fake unchanged. A real notifier can fail after the domain state changes or after a remote system accepts a request. Transaction, retry, idempotency, and acknowledgment policies require their own design. Do not infer them from a successful recording fake. Also avoid interpreting an empty catch in application code as a recommended recovery strategy; here it exists only around a deliberate invalid-input demonstration whose no-effect observation is then inspected.

Practice: isolate one behavior

First call Complete twice with two valid different codes and predict two messages in the same call order. Hint: this method does not promise deduplication, so repeated valid calls are separate requested effects. Then test whitespace-only input and confirm that it adds no message. Preserve both count and content checks. If the intended product contract instead requires one notice per order, define a stable identity and an idempotency policy before changing the test to reject repeated valid calls.

Practice: challenge another assumption

For another exercise, create a sink that deliberately throws and decide what Complete promises to its caller when that boundary fails. Hint: observe the real failure rather than silently reporting success. Keep that scenario separate from blank-input validation. Then write an integration-test plan for the actual notifier adapter, covering request mapping and supported failure responses, without sending messages from this portable example. Identify which statements the recording fake can establish and which depend on the external provider's actual behavior and configuration.

Review the evidence

The unchanged three lines prove one recorded valid notice and no additional notice after the rejected code. A good interaction test explains why those particular observations matter to the contract. It should remain valid when the service's private organization changes, while failing when a duplicate, missing, malformed, or forbidden effect appears. Use fake recordings to make an explicit boundary inspectable, and preserve the distinction between that local evidence and real-world delivery. Reliable application design requires both the right effect contract and appropriately scoped integration checks.

  • The recording sink stores messages locally and sends nothing externally.
  • The invalid code leaves its message count unchanged.
  • Verify effects that belong to the public boundary, not arbitrary private calls.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

var sink = new RecordingSink();
var confirmation = new Confirmation(sink);
confirmation.Complete("A1");
Console.WriteLine($"calls:{sink.Messages.Count}");
Console.WriteLine(sink.Messages[0]);
try { confirmation.Complete(""); } catch (ArgumentException) { }
Console.WriteLine($"calls-after-rejection:{sink.Messages.Count}");
interface INoticeSink { void Send(string text); }
sealed class RecordingSink : INoticeSink
{
    public List<string> Messages { get; } = new();
    public void Send(string text) => Messages.Add(text);
}
sealed class Confirmation
{
    private readonly INoticeSink sink;
    public Confirmation(INoticeSink sink) { this.sink = sink; }
    public void Complete(string code)
    {
        if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Code required", nameof(code));
        sink.Send("confirmed:" + code);
    }
}

Expected output

calls:1
confirmed:A1
calls-after-rejection:1
Try this
Record only the externally meaningful effect and verify no effect on rejection.

Further reading