Define the observable contract
A testable design reveals the capability it needs rather than locating infrastructure invisibly. ReservationPolicy receives an IAvailability and asks it for available units when evaluating a request. The interface is narrow: it exposes one relevant observation instead of an entire database client or application service locator. The example supplies FixedAvailability through ordinary construction, making the prepared value visible to the reader. This is a hand-written controlled collaborator, not a mock framework, and no container, network, or production repository is required to run the rule in the browser.
Walk through the real program
FixedAvailability stores three available units. ReservationPolicy accepts a positive quantity that does not exceed the value returned by its dependency. A request of two prints two:True; a request of four prints four:False. The dependency itself is not a changing stock store and does not reduce availability after a successful predicate. That limited contract is intentional. The constructor rejects a null source rather than letting an invalid dependency fail later during a request. Both the business comparison and the construction requirement are ordinary code that a test can observe explicitly.
Why the design is testable
Dependency inversion means the domain behavior depends on the abstraction it needs, while an adapter can implement that abstraction for a particular environment. The interface should be shaped by the caller's real contract, not by copying every method from a large infrastructure API. Production wiring may use a database adapter, but a successful fake-backed rule check does not prove that adapter's query or connection works. Separate a pure or controlled rule test from the integration tests that exercise the actual adapter and the end-to-end tests that cover the complete workflow.
A boundary that matters
A fake can disagree with reality if its behavior is too convenient or its contract is vague. For example, a constant availability source cannot reveal a race between two callers or a transactional capacity update. Do not claim those guarantees from these two booleans. Adding interfaces everywhere is also not automatically an improvement; a simple value parameter may be enough when no behavioral dependency needs to vary. Introduce the boundary where control, independent responsibilities, or environmental substitution provides a concrete benefit, and document assumptions such as whether availability can change during an operation.
Practice: isolate one behavior
First construct the policy with zero available units and predict rejection for both positive sample requests. Hint: the fake changes only one supplied observation; no production logic needs a special testing branch. Then use five available units and check exact-capacity acceptance plus one-unit excess. Keep a separate null-construction check for the dependency contract. Those cases should make the source's role clear without requiring a real database to prepare every small quantity comparison.
Practice: challenge another assumption
For another exercise, replace the fixed source with a small hand-written source whose next response is explicitly configured. Describe whether a request should query availability once and why that might be observable at the boundary. Hint: do not verify an arbitrary number of private helper calls merely because your fake can count them. Next sketch a real repository adapter and identify which mapping and infrastructure behavior require an integration test. Keep that project separate from the portable rule checks and avoid calling a fake-backed result proof of persistent stock consistency.
Review the evidence
The baseline demonstrates explicit construction and a narrow controlled capability. It does not simulate a production stock system. A reviewer should be able to identify the domain rule, the dependency's contract, the supplied fake behavior, and the untested integration boundary. Good substitution makes tests clearer by removing irrelevant uncertainty while leaving real obligations visible. When an abstraction hides rather than clarifies those obligations, simplify it or add the missing boundary evidence instead of increasing the number of mocks and assuming the design is now reliable.
- The fixed source supplies three units without changing them.
- The two predicate outcomes are True for two and False for four.
- Fake-backed rule evidence does not validate a real repository or transaction.
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 service = new ReservationPolicy(new FixedAvailability(3));
Console.WriteLine($"two:{service.CanReserve(2)}");
Console.WriteLine($"four:{service.CanReserve(4)}");
interface IAvailability { int GetAvailable(); }
sealed class FixedAvailability : IAvailability
{
private readonly int available;
public FixedAvailability(int available) { this.available = available; }
public int GetAvailable() => available;
}
sealed class ReservationPolicy
{
private readonly IAvailability source;
public ReservationPolicy(IAvailability source) { this.source = source ?? throw new ArgumentNullException(nameof(source)); }
public bool CanReserve(int quantity) => quantity > 0 && quantity <= source.GetAvailable();
}
Expected output
two:True four:False
A fake supplies one controlled capability; it is not a real database or network check.