Inferensys

Glossary

Test Double

A test double is a generic term for any object or component used in place of a real system dependency during testing to isolate the unit under test and control its environment.
Developer building agentic RAG system, retrieval pipeline diagram on laptop, technical workspace with notes.
SOFTWARE TESTING

What is a Test Double?

A foundational concept in automated testing for isolating components and controlling dependencies.

A test double is a generic term for any object, service, or component that replaces a real system dependency during automated testing to isolate the unit under test (UUT) and control its environment. This practice is central to unit testing and integration testing, enabling developers to verify logic in a deterministic, repeatable manner without relying on external systems that may be slow, unstable, or non-existent. Common types include mocks, stubs, fakes, spies, and dummies, each serving a specific role in simulating interactions.

The primary purpose of a test double is to provide isolation and determinism. By substituting a real database, API, or complex module, testers can inject specific responses, simulate error conditions, and verify interaction patterns (e.g., how many times a method was called). This is crucial for Test-Driven Development (TDD) and building reliable Automated API Testing Suites. Effective use of doubles, alongside practices like contract testing, ensures that integrations between components, such as an AI agent and an external tool, behave correctly as defined in their shared specifications.

GLOSSARY

Key Types of Test Doubles

A test double is a generic term for any object or component used in place of a real system dependency during testing to isolate the unit under test and control its environment. The following are specific implementations of this concept, each with distinct responsibilities and use cases.

01

Dummy

A Dummy is the simplest form of test double. It is an object passed to the system under test but never actually used. Its sole purpose is to satisfy a method's parameter list.

  • Purpose: To fill mandatory parameters where the actual value is irrelevant to the test.
  • Characteristics: Often null, an empty object, or a primitive default value.
  • Example: Passing a null User object to a method when testing a Logger class that doesn't interact with the user data.
02

Stub

A Stub provides canned answers to calls made during the test. It is programmed to return specific, hard-coded data in response to method calls, overriding the real dependency's behavior.

  • Purpose: To control the indirect inputs to the system under test.
  • Characteristics: Contains no logic; returns predefined data. Does not respond to anything outside its programmed responses.
  • Example: A stub for a WeatherService that always returns "Sunny" to test how a TripPlanner behaves under good weather conditions.
03

Spy

A Spy is a type of stub that also records information about how it was called. It captures invocation details such as the number of calls, arguments passed, and the order of calls for later verification by the test.

  • Purpose: To observe and make assertions about the interactions between the system under test and its dependencies.
  • Characteristics: Passively records activity. Can also stub return values if needed.
  • Example: A spy on an EmailSender to verify that the send() method was called exactly once with a specific recipient address after a user registration.
04

Mock

A Mock is an object pre-programmed with expectations about the calls it will receive. It verifies that these expected interactions occur. A test using a mock will fail if the expected calls are not made or are made in the wrong order.

  • Purpose: To test the behavior (the outgoing commands) of the system under test.
  • Characteristics: Defined by expectations set before the test executes. Verifies those expectations afterward. Often created using a mocking framework (e.g., Mockito, unittest.mock).
  • Example: A mock PaymentGateway that expects a charge(amount, card) call with specific arguments. The test asserts this interaction happened.
05

Fake

A Fake is a working, but simplified and lightweight, implementation of a dependency. It has functional behavior but uses shortcuts unsuitable for production, such as an in-memory database instead of a real SQL database.

  • Purpose: To replace a heavyweight, slow, or complex dependency with a functional equivalent that is good enough for testing.
  • Characteristics: Contains real business logic but uses simplified infrastructure. It is not a stub—it has real, working internals.
  • Example: A FakeUserRepository that stores users in a HashMap instead of a PostgreSQL database, allowing for fast, isolated data layer tests.
06

Choosing the Right Double

Selecting the appropriate test double is critical for clear, maintainable tests. Follow this guideline:

  • Use a Dummy for unused required parameters.
  • Use a Stub to force the system down a specific path (control inputs).
  • Use a Spy to query how a dependency was used after the fact.
  • Use a Mock to define and verify specific behavioral expectations.
  • Use a Fake to replace a complex infrastructure component with a functional, test-friendly version.

Key Principle: Prefer stubs and fakes over mocks when possible, as they test state over interaction, leading to less brittle tests.

TYPES & USE CASES

Test Double Comparison

A comparison of the five primary types of test doubles, detailing their purpose, implementation complexity, and typical use cases in automated API testing.

TypePurposeImplementationRealismUse Case

Dummy

Fulfill mandatory parameter requirements.

Null or empty placeholder object.

Passing an unused object to a constructor during a unit test.

Stub

Provide canned, pre-programmed responses to specific calls.

Hard-coded return values for a subset of methods.

Simulating a simple "success" or "failure" response from an external payment API.

Mock

Verify interactions ("how" the unit under test calls its dependencies).

Pre-programmed expectations about method calls (e.g., 'method X must be called once with param Y').

Ensuring an audit logging service is called exactly once after a transaction.

Spy

Record interactions for later verification without enforcing expectations upfront.

Wraps a real object or stub and passively records method invocations and arguments.

Capturing the arguments sent to an email service to verify content, without failing the test if the call isn't made.

Fake

Provide a fully functional, but simplified, alternative implementation.

Lightweight, in-memory working implementation (e.g., a fake database or API client).

Replacing a cloud-based NoSQL database with an in-memory hash map for fast, isolated integration tests.

TEST DOUBLE

Frequently Asked Questions

A test double is a generic term for any object or component used in place of a real system dependency during testing to isolate the unit under test and control its environment. This FAQ addresses common questions about their implementation and role in automated API testing.

A test double is a stand-in object or component that replaces a real dependency in a software system during automated testing. It works by implementing the same interface as the real dependency but provides controlled, predictable responses, allowing developers to isolate the unit under test (UUT) from external systems like databases, APIs, or third-party services. By intercepting calls meant for the real component, a test double eliminates non-determinism, network latency, and side effects, creating a hermetic test environment. This isolation is fundamental to unit testing and integration testing within a continuous testing pipeline.

Prasad Kumkar

About the author

Prasad Kumkar

CEO & MD, Inference Systems

Prasad Kumkar is the CEO & MD of Inference Systems and writes about AI systems architecture, LLM infrastructure, model serving, evaluation, and production deployment. Over 5+ years, he has worked across computer vision models, L5 autonomous vehicle systems, and LLM research, with a focus on taking complex AI ideas into real-world engineering systems.

His work and writing cover AI systems, large language models, AI agents, multimodal systems, autonomous systems, inference optimization, RAG, evaluation, and production AI engineering.