CLOUD CONCEPT

Test Doubles

Test doubles are controlled substitutes for real components or dependencies used to exercise application behavior during testing.

Cloud Concepts Hub →

What is Test Doubles

Test doubles let engineers test application behavior without relying on every production dependency being available, predictable, or safe to call. They make development tests faster and more repeatable, but must be complemented by tests against real integrations to detect configuration problems and differences from actual service behavior.

A test double replaces a component that an application would normally use, such as an HTTP API, database client, payment provider, or message publisher. The replacement provides controlled behavior so a test can exercise a specific scenario. For cloud applications, this is particularly useful when dependencies incur charges, enforce rate limits, contain sensitive data, or perform irreversible actions such as sending notifications or charging a card.

Test double is an umbrella term, not a synonym for one implementation technique. A stub returns predefined responses. A mock checks expected interactions, such as whether a payment request contains the correct idempotency key. A spy records calls for later inspection. A fake provides a working but simplified implementation, such as an in-memory repository. A dummy satisfies a required parameter without participating in the scenario. Testing libraries sometimes use these labels differently, so define what behavior each replacement provides.

Make dependencies replaceable through interfaces, constructor parameters, client factories, or configurable endpoint URLs. For example, an order service can receive a payment client rather than constructing a production client inside its business logic. A unit test can inject a stub client that returns a declined payment. An integration test can run the actual order handler, serialization code, and HTTP client while directing payment requests to a mock HTTP server. That test exercises the application's integration with the substitute, not its compatibility with the real payment provider.

In a cloud development environment, a mock API can expose representative HTTP methods, paths, status codes, headers, and response bodies. Amazon API Gateway REST APIs support mock integrations that return configured responses without calling a backend. A dedicated mock server running in a container is more suitable when scenarios require stateful behavior, deliberate delays, connection failures, or complex request matching. Keep mock endpoint configuration separate from production configuration, and prevent tests from falling back to production endpoints when configuration is missing.

Test more than successful responses. A payment integration should handle validation failures, authorization errors, throttling, malformed responses, and transient server errors. Configure response sequences to verify retry limits and backoff behavior. Simulate a timeout after a payment has been accepted to check that retries reuse the same idempotency key. For asynchronous workflows, verify how the application handles duplicate messages and out-of-order delivery where the real system permits them. Reset fake state and recorded requests between tests so parallel runs do not interfere.

The main trade-off is fidelity versus speed and control. An in-process stub is inexpensive and deterministic, but bypasses networking and serialization. A remote mock API exercises more of the request path, but introduces deployment overhead and may still omit authentication, service limits, consistency behavior, and infrastructure permissions. Replacing an SDK method does not prove that the deployed application has the correct IAM permissions or network access.

Keep doubles aligned with documented contracts and representative, sanitized examples. Contract tests can detect disagreements about request and response formats; where practical, verify those contracts against the provider. Maintain a smaller set of integration or end-to-end tests against isolated real resources or an external provider's sandbox. Passing tests against doubles establishes behavior under the modeled conditions, not complete production compatibility. In development and deployment pipelines, use doubles for repeatable scenario coverage and real integrations for the boundaries that substitutes cannot validate.

Key aspects of Test Doubles

Effective test doubles provide controlled dependency behavior while making their test scope, isolation requirements, and fidelity limits explicit.

Explicit dependency boundaries

Inject clients or configure dependency endpoints so tests can replace external systems without changing business logic. Keep production and test configuration separate, and fail safely when a required test endpoint is missing.

Controlled responses and interactions

Use stubs for predefined outcomes, mocks for interaction expectations, spies for recorded calls, and fakes for simplified working behavior. Choose the replacement according to what the test must establish rather than treating every double as a mock.

Repeatable failure scenarios

Model throttling, timeouts, malformed payloads, and transient failures to exercise recovery behavior. Verify observable outcomes such as bounded retries, preserved idempotency keys, and appropriate error handling.

Deliberate integration scope

An HTTP mock can exercise real request construction and response parsing while replacing the remote provider. Document which boundaries remain real so passing tests are not mistaken for proof of production permissions, connectivity, or provider compatibility.

Contract fidelity and test isolation

Maintain representative schemas and response semantics, and reset state between tests. Complement doubles with contract verification and selected tests against real isolated resources to catch behavior that the substitutes do not reproduce.

Cloud services for Test Doubles

API Gateway REST API mock integrations provide simple configured HTTP responses, while a mock server deployed on ECS supports more complex dependency behavior. CodeBuild runs application tests against these endpoints or against doubles injected directly into the test process. Keep endpoints and scenario data isolated for each test environment, and restrict access to development callers. These services support repeatable integration testing without invoking every external provider, but they do not establish compatibility with the real dependency. Retain targeted tests against isolated real resources or provider sandboxes.

Amazon API Gateway image Amazon API Gateway

REST APIs support mock integrations that return configured responses without invoking a backend, allowing a development application to call a substitute HTTP endpoint. This capability is specific to REST APIs and should not be assumed to exist for HTTP APIs.

Open Amazon API Gateway →
AWS CodeBuild image AWS CodeBuild

CodeBuild can run automated tests that inject in-process doubles or call mock endpoints during a build. Configure isolated test data and endpoints, and fail the build when test assertions fail; CodeBuild itself does not supply the dependency behavior.

Open AWS CodeBuild →