Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java test boundaries: injected collaborators and behavior-focused checks

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

An injected collaborator lets a test supply a controlled dependency while checking the caller’s validation, result and failure contract.

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

Java
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

Output
4 gateway checks passed

Costs 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.

java
test-boundaries
Storage details