An injected collaborator lets a test supply a controlled dependency while checking the caller’s validation, result and failure contract.
Java test boundaries: injected collaborators and behavior-focused checks
Java 8+. This is a complete program using JDK classes.
Replace the dependency, not the assertion
A quote operation needs a supplier amount and adds a fixed fee. The test supplies a deterministic gateway, so it does not depend on network access. A second gateway throws an I/O failure, letting the test verify that failure remains visible to the caller.
Another check passes an invalid destination and a gateway that must never be called. That verifies validation occurs before external work. It is a behavior requirement, not an assertion about private helper names or the number of internal methods.
This hand-written test double needs no mocking framework. A framework can create doubles for larger APIs, but the meaningful decision is still what behavior the application promises. A stubbed collaborator also does not replace a separate integration test against the real protocol.
Working program
import java.io.IOException;
public class QuoteBoundaryChecks {
interface Gateway{int amount(String destination)throws IOException;}
static int total(String destination,Gateway gateway)throws IOException{
if(destination==null||destination.trim().isEmpty())throw new IllegalArgumentException("Missing destination");
int base=gateway.amount(destination);
if(base<0)throw new IllegalArgumentException("Negative quote");
return Math.addExact(base,10);
}
public static void main(String[] args)throws Exception{
if(total("Delhi",destination->90)!=100)throw new AssertionError("Fee changed");
try{total("",destination->{throw new AssertionError("Invalid request called gateway");});throw new AssertionError("Missing destination accepted");}
catch(IllegalArgumentException expected){ }
try{total("Delhi",destination->{throw new IOException("Supplier unavailable");});throw new AssertionError("Failure hidden");}
catch(IOException expected){ }
try{total("Delhi",destination->-1);throw new AssertionError("Negative quote accepted");}
catch(IllegalArgumentException expected){ }
System.out.println("4 gateway checks passed");
}
}Output
4 gateway checks passedCosts and boundaries
These tests do constant-sized local work; a real gateway contributes I/O, queueing and decoding costs. Controlled doubles isolate application behavior but cannot measure external latency, wire compatibility or the supplier’s own validation. Checked arithmetic preserves the overflow boundary when adding a fee.
Common Mistakes
- A fake success response is not a protocol integration test.
- Test doubles should implement the dependency contract the caller actually uses.
- Do not make tests pass merely by mirroring the private implementation.
Read next
Executable tests, Local HTTP integration.
Apply this contract in Spring
Spring tests: separate business rules, wiring and transport, Spring ApplicationContextRunner: check conditional assembly in isolation. These lessons keep framework assembly separate from the Java contract.
