0
0
JUnittesting~15 mins

Why different doubles serve different purposes in JUnit - Why It Works This Way

Choose your learning style9 modes available
Overview - Why different doubles serve different purposes
What is it?
In software testing, 'test doubles' are fake objects that stand in for real parts of a program during tests. Different types of doubles like mocks, stubs, spies, and fakes serve different roles to help test specific behaviors or states. They let us isolate the part we want to test without relying on other complex or slow parts. This helps tests run faster and be more reliable.
Why it matters
Without using the right kind of test double, tests can become slow, flaky, or misleading. For example, if you use a real database in every test, tests might fail due to network issues, not code bugs. Different doubles solve this by mimicking only what is needed, making tests focused and trustworthy. This saves time and helps developers catch real problems quickly.
Where it fits
Before learning about test doubles, you should understand basic unit testing and why isolation matters. After this, you can learn how to write tests using specific doubles with JUnit and Mockito, and how to design tests for complex systems using integration and end-to-end testing.
Mental Model
Core Idea
Different test doubles act like specialized helpers that replace real parts in tests, each designed to simulate specific behaviors or interactions to make tests clear and reliable.
Think of it like...
Imagine testing a car's engine without using a real fuel tank. You use different fake fuel tanks: one just gives fuel (stub), one records how much fuel was used (spy), one checks if the engine asked for fuel correctly (mock), and one behaves like a simple real tank (fake). Each helps test the engine differently.
Test Double Types
┌─────────┬───────────────┬───────────────────────────────┐
│ Type    │ Purpose       │ Behavior                      │
├─────────┼───────────────┼───────────────────────────────┤
│ Stub    │ Provide data  │ Returns preset responses      │
│ Mock    │ Verify calls  │ Checks if expected calls made │
│ Spy     │ Record calls  │ Records interactions          │
│ Fake    │ Simplified real│ Implements working logic      │
└─────────┴───────────────┴───────────────────────────────┘
Build-Up - 7 Steps
1
FoundationWhat Are Test Doubles
🤔
Concept: Introduce the idea of replacing real components with test doubles in unit tests.
When testing a small part of code, sometimes the parts it depends on are complex or slow. Test doubles are simple replacements that let us test just the part we want. For example, instead of calling a real database, we use a fake that returns fixed data.
Result
Tests become faster and more focused because they don't rely on real external parts.
Understanding that test doubles isolate the unit under test is key to writing reliable tests.
2
FoundationTypes of Test Doubles Overview
🤔
Concept: Learn the main types of test doubles and their basic roles.
There are four main types: stubs, mocks, spies, and fakes. Stubs provide fixed answers to calls. Mocks check if certain calls happened. Spies record calls for later checks. Fakes are simple working versions of real components.
Result
You can now name and distinguish the basic test doubles.
Knowing the types helps choose the right double for the testing need.
3
IntermediateUsing Stubs to Control Test Data
🤔Before reading on: do you think stubs can verify if a method was called or only provide data? Commit to your answer.
Concept: Stubs replace parts that provide data, returning preset values without checking interactions.
In JUnit tests, a stub might be a class that returns fixed data when a method is called. For example, a stub database returns a fixed user object regardless of input. This helps test how the code behaves with known data.
Result
Tests can simulate different data scenarios without real databases.
Understanding stubs prevent tests from depending on unpredictable external data.
4
IntermediateMocks Verify Interactions
🤔Before reading on: do you think mocks only provide data or also check if methods were called? Commit to your answer.
Concept: Mocks are test doubles that check if certain methods were called with expected arguments.
Using Mockito with JUnit, you create a mock object and after running the test, verify if a method was called. For example, verifying if a notification service was called when an order is placed. This ensures the code interacts correctly with dependencies.
Result
Tests confirm not just outcomes but also correct behavior.
Knowing mocks help test the 'how' of code behavior, not just the 'what'.
5
IntermediateSpies Record Real Calls
🤔Before reading on: do you think spies replace real objects completely or wrap them to record calls? Commit to your answer.
Concept: Spies wrap real objects to record how they are used during tests.
A spy lets the real methods run but also records calls and arguments. For example, spying on a list to check which methods were called while still using the real list behavior. This helps when you want to test real logic but also verify interactions.
Result
Tests combine real behavior with interaction checks.
Understanding spies bridge the gap between mocks and real objects.
6
AdvancedFakes as Simplified Real Implementations
🤔Before reading on: do you think fakes are just mocks or do they implement real logic? Commit to your answer.
Concept: Fakes are lightweight implementations that behave like real components but are simpler and faster.
A fake database might store data in memory instead of a real database. It supports real operations but is easier to use in tests. This helps test complex logic without slow or fragile real systems.
Result
Tests run fast and simulate realistic scenarios.
Knowing fakes provide a balance between realism and test speed.
7
ExpertChoosing the Right Double in Complex Tests
🤔Before reading on: do you think using many mocks always improves test quality or can it cause problems? Commit to your answer.
Concept: Selecting the appropriate double type depends on test goals and complexity; overusing mocks can make tests brittle.
In large systems, using too many mocks can cause tests to fail when internal details change, even if behavior is correct. Experts balance mocks, stubs, spies, and fakes to keep tests stable and meaningful. For example, use fakes for stable components and mocks only for critical interaction checks.
Result
Tests remain maintainable and trustworthy over time.
Understanding the tradeoffs in double usage prevents fragile tests and wasted effort.
Under the Hood
Test doubles work by replacing real objects in the code under test, either by subclassing, proxying, or implementing interfaces. Frameworks like Mockito generate proxy objects at runtime that intercept method calls. Stubs return preset values without side effects. Mocks record calls and verify expectations after test execution. Spies wrap real objects, forwarding calls while recording them. Fakes implement simplified logic to simulate real behavior.
Why designed this way?
Test doubles were designed to isolate units of code to test them independently. Early testing faced slow or unreliable dependencies like databases or web services. Different doubles evolved to address specific testing needs: stubs for data control, mocks for interaction verification, spies for partial observation, and fakes for realistic but fast substitutes. This separation helps keep tests fast, reliable, and focused.
Test Double Mechanism
┌───────────────┐
│ Unit Under   │
│ Test (UUT)   │
└──────┬────────┘
       │
       │ depends on
       ▼
┌───────────────┐
│ Real Component│
│ (e.g., DB)    │
└───────────────┘

Replace with:

┌───────────────┐
│ Test Double   │
│ ┌───────────┐ │
│ │ Stub      │ │
│ │ Mock      │ │
│ │ Spy       │ │
│ │ Fake      │ │
│ └───────────┘ │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do mocks provide preset data like stubs or only verify calls? Commit to yes or no.
Common Belief:Mocks are just like stubs that return fixed data.
Tap to reveal reality
Reality:Mocks primarily verify that certain methods were called with expected arguments; they do not just provide data.
Why it matters:Using mocks as stubs can lead to tests that don't check interactions properly, missing bugs in how code uses dependencies.
Quick: Can spies replace real objects completely or do they wrap them? Commit to your answer.
Common Belief:Spies are full replacements that do not use real object behavior.
Tap to reveal reality
Reality:Spies wrap real objects, allowing real methods to run while recording calls for verification.
Why it matters:Misunderstanding spies can cause tests to miss real behavior or fail to record needed interactions.
Quick: Is it always better to use mocks for every dependency? Commit to yes or no.
Common Belief:Using mocks for all dependencies makes tests better and more reliable.
Tap to reveal reality
Reality:Overusing mocks can make tests fragile and tightly coupled to implementation details, causing frequent breaks on refactoring.
Why it matters:Tests become hard to maintain and trust, slowing down development.
Quick: Do fakes have to implement full real logic or can they be simplified? Commit to your answer.
Common Belief:Fakes must fully replicate real component behavior exactly.
Tap to reveal reality
Reality:Fakes implement simplified versions that are good enough for tests but not full production logic.
Why it matters:Trying to make fakes too complex wastes time and defeats their purpose of fast, simple testing.
Expert Zone
1
Mocks verify behavior but can cause brittle tests if overused; balancing with stubs and fakes improves test resilience.
2
Spies are useful when you want to observe real object behavior without fully replacing it, especially in legacy code.
3
Fakes can be used to simulate complex systems like databases or web services in memory, speeding up integration tests.
When NOT to use
Avoid mocks when testing stable, well-defined components; prefer fakes or real implementations to reduce test fragility. For end-to-end tests, use real components instead of doubles to verify full system behavior.
Production Patterns
In professional projects, teams use mocks to verify critical interactions like API calls, stubs for data setup, spies for partial observation, and fakes for fast integration tests. Test frameworks like Mockito integrate these doubles seamlessly with JUnit for clear, maintainable tests.
Connections
Dependency Injection
Test doubles rely on dependency injection to replace real components with doubles during tests.
Understanding dependency injection helps grasp how test doubles are swapped in without changing production code.
Behavior-Driven Development (BDD)
Mocks and spies are often used in BDD to verify that code behaves as expected in scenarios.
Knowing test doubles enhances writing clear, behavior-focused tests that describe system interactions.
Theatre Acting
Like actors playing different roles to simulate characters, test doubles play roles of real components to simulate behavior.
Recognizing this connection highlights the importance of role fidelity and interaction in both acting and testing.
Common Pitfalls
#1Using mocks to return data instead of verifying calls.
Wrong approach:when(mockedService.getData()).thenReturn("data"); // Using mock as stub
Correct approach:when(stubbedService.getData()).thenReturn("data"); // Use stub for data
Root cause:Confusing the purpose of mocks and stubs leads to tests that don't verify interactions properly.
#2Overusing mocks for every dependency causing fragile tests.
Wrong approach:Mocking every collaborator even when not needed, verifying all calls strictly.
Correct approach:Use fakes or real implementations for stable components; mock only critical interactions.
Root cause:Misunderstanding that mocks verify behavior leads to brittle tests tightly coupled to implementation.
#3Using spies without wrapping real objects, losing real behavior.
Wrong approach:Spy created as a mock without real method calls: spy = mock(Class.class);
Correct approach:Spy created by wrapping real object: spy = spy(new Class());
Root cause:Not knowing spies wrap real objects causes loss of real logic during tests.
Key Takeaways
Test doubles are fake objects that replace real parts in tests to isolate and control behavior.
Different doubles serve different purposes: stubs provide data, mocks verify calls, spies record interactions, and fakes simulate real logic simply.
Choosing the right double type improves test speed, reliability, and clarity.
Overusing mocks can make tests fragile; balancing doubles based on test goals is essential.
Understanding how doubles work under the hood helps write better, maintainable tests.