Mocks, stubs, and fakes are all types of test doubles that replace real components in a test environment to help isolate the unit under test. While they may appear similar, each serves a distinct purpose in different testing scenarios.
Overview
What Are Test Doubles?
Test doubles are controlled stand-ins for real dependencies. They allow you to test your code in isolation without relying on external systems like APIs, databases, or third-party services. By using test doubles, you can simulate specific behaviors, control outputs, and verify interactions.
Mock vs Stub vs Fake: Major Differences
The table below outlines the core differences between mocks, stubs, and fakes, based on what testers typically care about when deciding which one to use:
Type | Purpose | Focus | Use Case Example |
Mock | Verify how dependencies are used | Behavior (calls, order) | Confirm if a method was called correctly |
Stub | Provide predefined responses | Output | Simulate an API or service response |
Fake | Provide working but simplified logic | Functional substitute | Use an in-memory DB instead of a real one |
This article discusses the key differences between mocks, stubs, and fakes and how to choose the right one for your tests.
What is a Mock?
A mock is an object that is used to verify interactions between the unit being tested and its dependencies. It is not concerned with the actual output but with the behavior of the system. Mocks can verify whether certain methods were called, how many times they were called, or in what order they were called.
When and Why to Use a Mock
Mocks are particularly useful when you want to ensure that a certain interaction or behavior happens in your code. For example, you might use a mock to verify that a method in your class interacts with an external service (like a database or an API) in the expected manner.
API mocking allows you to test if specific methods were called with the right arguments, which can be critical when dealing with external dependencies.
Mocks are essential for:
- Verifying the number of times a method is called.
- Checking the arguments passed to a method.
- Ensuring specific methods are called in a particular sequence.
Mocks are generally used when the exact output isn’t as important as how the system behaves in response to calls. They help improve test reliability by focusing on behavior rather than state.
What is a Stub?
A stub is an object that provides predefined responses to method calls. Unlike mocks, which focus on verifying behavior, stubs are concerned with providing controlled outputs to simulate real-world scenarios. This makes them useful for isolating components that depend on external systems, like databases or third-party services.
When and Why to Use a Stub
Stubs are ideal when you need to simulate specific behaviors or data for your tests. If your test depends on an external service (e.g., a payment gateway or a user authentication system) and you don’t want to rely on the actual service, you can use a stub to simulate the responses from that service.
By providing consistent and controlled responses, stubs allow you to test your system’s behavior in isolation without being affected by external changes.
You might use a stub when:
- Testing code that relies on external services that may be slow, unreliable, or unavailable.
- Simulating responses from APIs or modules that are too complex to use directly in the test environment.
- Ensuring that your code behaves correctly under certain predefined conditions without worrying about the actual implementation of the external services.
What is a Fake?
A fake is a working implementation of an interface or class that is used in place of the real object. It behaves like the real system but with simplified or limited functionality. Fakes are often used when the real implementation is too slow or complex for testing purposes, but you still need some form of realistic behavior.
When and Why to Use a Fake
Fakes are useful when you need to simulate a working component that has simplified or less complex functionality compared to the real system.
For example, in testing scenarios where you need a database but don’t want to use the actual database, you might use an in-memory database as a fake. This allows you to test your logic without relying on external systems or dealing with the overhead of real implementations.
Fakes are typically used when:
- Testing performance or behavior with a simplified version of a real system.
- Using a component like an in-memory database that mimics the behavior of a real database without the associated complexity or performance hit.
- Providing realistic but lightweight implementations to test core business logic without unnecessary overhead.
Examples of Mocks, Stubs, and Fakes in Testing
Understanding mocks, stubs, and fakes becomes clearer when seen in the context of real features. Let’s take a banking application and walk through three specific test cases, each highlighting how a different type of test double can help.
1. Mocking Service Interactions with Verification
In a banking app, consider a feature that sends a notification to the user after a successful money transfer. The notification service is external, and your goal is to verify that it was called correctly.
In this case, use a mock to confirm that your code interacts with the notification service as expected.
// Create a mock for the NotificationService NotificationService notificationService = mock(NotificationService.class); MoneyTransferService service = new MoneyTransferService(notificationService); // Trigger the transfer service.transfer("user123", "user456", 500); // Verify that the notification was sent verify(notificationService).sendTransferSuccess("user123", 500);
Why use a mock?
You’re not interested in whether the notification was actually sent. You just want to confirm that your code made the correct call.
2. Stubbing Unavailable APIs or Modules
Suppose the banking app integrates with a third-party fraud detection API. When testing your transaction logic, you don’t want to rely on the real API, which could be slow or unavailable.
Here, a stub helps by simulating the API’s response.
// Stub the FraudCheckAPI to return "no fraud" FraudCheckAPI fraudApi = mock(FraudCheckAPI.class); when(fraudApi.isFraudulent(any())).thenReturn(false); MoneyTransferService service = new MoneyTransferService(fraudApi); // Proceed with the transfer logic boolean result = service.transfer("user123", "user456", 1000); assertTrue(result); // Transaction goes through
Why use a stub?
You’re focused on how your logic behaves when the fraud check passes. The stub gives you a predictable response without needing the real API.
3. Faking Data Repositories or In-Memory Objects
Let’s say you’re testing account balance updates after a transfer. You don’t want to hit the real database during tests, but you still need realistic behavior.
A fake repository can simulate actual data persistence using an in-memory structure.
// Use a fake in-memory repository InMemoryAccountRepository fakeRepo = new InMemoryAccountRepository(); fakeRepo.save(new Account("user123", 2000)); fakeRepo.save(new Account("user456", 1000)); MoneyTransferService service = new MoneyTransferService(fakeRepo); // Execute the transfer service.transfer("user123", "user456", 500); // Verify balances updated correctly assertEquals(1500, fakeRepo.find("user123").getBalance()); assertEquals(1500, fakeRepo.find("user456").getBalance());
Why use a fake?
You need realistic read/write behavior to test your logic, but without the cost or complexity of connecting to a real database.
Mock vs Stub vs Fake: Key Differences
Mocks, stubs, and fakes all help isolate code during testing, but they are not interchangeable. Each one is designed to support a different type of test. The main differences come down to what the test is trying to verify, how much control you need over dependencies, and whether realistic behavior is required.
The table below outlines the key differences:
Aspect | Mock | Stub | Fake |
---|---|---|---|
Primary Purpose | Check if methods were called as expected | Return controlled responses for test scenarios | Provide a simplified working implementation |
What It Focuses On | Method calls, arguments, and call order | Output values based on inputs | Logic that mimics real behavior with fewer resources |
Used When | You need to verify that an interaction took place | You want to isolate your test from external systems | You want to test with functional behavior without real services |
Simulates Real Behavior | No | No | Yes |
Verifies Interaction | Yes | No | No |
Depends on Real Logic | No | No | Yes |
Risk if Overused | Can lead to fragile tests | Generally low | Can hide integration issues if used for too long |
Common Use Case | Check if a notification was triggered | Simulate a third-party API returning success | Replace the database with in-memory storage |
How Requestly Supports Mocks, Stubs, and Fakes
Requestly is a no-code platform that allows you to intercept, mock, and modify HTTP requests and responses directly in your browser or development environment. It helps simulate different kinds of test doubles without needing to rewrite or deploy backend code.
You can use Requestly as:
- A stub, by serving fixed responses for specific endpoints
- A mock, by observing and controlling how requests are triggered and processed
- A fake, by injecting simplified but functional logic into API responses
Here are the key features of Requestly that support Mocks, Stubs, and Fakes:
- Modify API Response: Return custom payloads, status codes, or headers for any HTTP request. This helps stub unavailable or unstable APIs during development and testing.
- Inject JavaScript-Based Logic: Add conditional logic to responses using JavaScript. This lets you simulate fake services that behave differently based on inputs.
- Simulate Delays and Errors: Control response timing or simulate API failures to test how your application handles timeouts, server errors, or missing data.
- Intercept GraphQL Queries: Match GraphQL operations by name and return tailored responses. This is useful when only part of the backend is available or ready.
- Generate Mocks from Real Sessions: Record API traffic and convert it into multiple mock rules automatically. This helps test entire flows without setting up mock data manually.
Mistakes to Avoid When Using Mocks, Stubs, and Fakes
While test doubles like mocks, stubs, and fakes are powerful tools, they should be used with care. Here are some common mistakes to avoid:
- Overusing Test Doubles for Every Test: Using test doubles is useful, but overusing them can lead to overly complex tests. Always ask whether the test genuinely benefits from a test double or if it can be simplified.
- Making Test Doubles Too Intelligent or Complex: Test doubles should not replace the real system entirely. Keep them simple and focused on the behavior you need to test, avoiding unnecessary complexity.
- Mixing Different Types of Doubles in a Single Test: Mixing mocks, stubs, and fakes in the same test can make it difficult to understand the test’s purpose. Each test should focus on a single concern, and introducing multiple types of doubles can blur that focus.
- Failing to Update Test Doubles as Your Code Evolves: As your codebase evolves, so too should your test doubles. Failing to update them can lead to tests that no longer reflect the actual behavior of your system.
- Treating Test Doubles as Permanent Solutions: Test doubles are meant for testing, not as long-term solutions. Ensure that they are replaced with real implementations when possible, and avoid relying on them for everything.
- Ignoring Test Double Dependencies: Test doubles may have dependencies (e.g., libraries or frameworks) that should be carefully considered. Ignoring these can result in unrealistic tests that don’t account for all the variables.
Conclusion
Mocks, stubs, and fakes are useful for isolating dependencies and controlling how different parts of an application behave during tests. Mocks help verify that expected interactions occur during a test, while stubs return predefined outputs. Fakes, on the other hand, use in-memory logic or storage to replace real systems like databases or services during testing.
Requestly supports all three by letting you modify and simulate network behavior without touching the code. You can mock HTTP requests, stub API responses, or fake external systems directly in the browser or across shared environments. This makes it easier to create realistic test scenarios without adding extra complexity to your test setup.