0
0
JUnittesting~15 mins

Fake objects in JUnit - Deep Dive

Choose your learning style9 modes available
Overview - Fake objects
What is it?
Fake objects are simple implementations of interfaces or classes used in testing to replace real components. They provide controlled behavior and data, allowing tests to run without relying on real external systems. Unlike mocks or stubs, fakes usually have working implementations but are simplified versions. They help isolate the code under test by simulating parts it interacts with.
Why it matters
Without fake objects, tests would depend on real systems like databases or web services, making tests slow, unreliable, or hard to run. Fakes let you test your code quickly and safely by controlling external dependencies. This improves confidence in your code and speeds up development. Without fakes, testing complex systems would be much harder and error-prone.
Where it fits
Before learning fake objects, you should understand basic unit testing and the role of test doubles like mocks and stubs. After mastering fakes, you can explore advanced test doubles, mocking frameworks, and integration testing strategies. Fake objects fit in the middle of the testing journey, bridging simple stubs and full mocks.
Mental Model
Core Idea
Fake objects are lightweight, working substitutes that mimic real components to isolate and speed up tests.
Think of it like...
Using a fake object is like practicing a dance routine with a cardboard partner instead of a real person—you get the feel and timing without the complexity or unpredictability.
┌───────────────┐       ┌───────────────┐
│   Test Code   │──────▶│   Fake Object │
└───────────────┘       └───────────────┘
         │                      ▲
         │                      │
         ▼                      │
  ┌───────────────┐             │
  │ Real Component│◀────────────┘
  └───────────────┘

Fake object replaces the real component during testing to control behavior.
Build-Up - 6 Steps
1
FoundationUnderstanding Test Doubles Basics
🤔
Concept: Introduce the idea of test doubles as substitutes for real components in tests.
In testing, sometimes you don't want to use the real parts of your system because they might be slow or unreliable. Test doubles are fake versions of these parts that help tests run smoothly. Examples include stubs, mocks, and fakes. Each has a different role in controlling or verifying behavior.
Result
Learners understand why and when to replace real components in tests.
Knowing test doubles exist helps you avoid slow or flaky tests by isolating the code under test.
2
FoundationWhat Are Fake Objects Exactly
🤔
Concept: Define fake objects as simple, working implementations used in place of real components.
Fake objects are like mini versions of real components. They have actual code that runs but are simpler and faster. For example, a fake database might store data in memory instead of on disk. This lets tests run quickly without needing the real database.
Result
Learners can distinguish fakes from other test doubles like mocks or stubs.
Understanding that fakes have working code helps you choose them when you need realistic behavior without complexity.
3
IntermediateCreating a Simple Fake Object in JUnit
🤔Before reading on: do you think a fake object needs to implement all methods of the real component or just the ones used in tests? Commit to your answer.
Concept: Show how to write a fake object by implementing an interface with minimal working code.
Suppose you have an interface UserRepository with methods to add and find users. A fake can implement this interface using a simple list to store users in memory. This fake can be used in tests instead of a real database. Example: public class FakeUserRepository implements UserRepository { private List users = new ArrayList<>(); @Override public void addUser(User user) { users.add(user); } @Override public User findUser(String id) { return users.stream().filter(u -> u.getId().equals(id)).findFirst().orElse(null); } } In your JUnit test, you create FakeUserRepository and pass it to the code under test.
Result
Tests run faster and independently of real databases, using the fake repository.
Knowing you only need to implement used methods keeps fakes simple and focused.
4
IntermediateWhen to Use Fakes vs Mocks or Stubs
🤔Before reading on: do you think fakes are better for verifying interactions or for providing working behavior? Commit to your answer.
Concept: Explain the differences and when to prefer fakes over other test doubles.
Mocks are mainly for verifying that certain methods were called. Stubs provide fixed responses but usually don't have real logic. Fakes have working logic and can be used when you want realistic behavior without the real system. Use fakes when you want your tests to behave like the real system but faster and simpler.
Result
Learners can choose the right test double for their testing needs.
Understanding the strengths of each test double type helps write clearer, more maintainable tests.
5
AdvancedHandling State and Side Effects in Fakes
🤔Before reading on: do you think fakes should perfectly mimic all side effects of real components? Commit to your answer.
Concept: Discuss how fakes manage internal state and side effects to simulate real behavior without full complexity.
Fakes often keep internal state, like storing data in memory. However, they usually simplify or skip complex side effects like network calls or transactions. For example, a fake payment processor might just record payments without contacting a bank. This balance keeps tests fast and reliable while still realistic enough.
Result
Learners understand how to design fakes that are useful but not overly complex.
Knowing how to balance realism and simplicity prevents fakes from becoming as complex as real components.
6
ExpertAvoiding Common Pitfalls with Fake Objects
🤔Before reading on: do you think using fakes can sometimes hide bugs that only appear with real components? Commit to your answer.
Concept: Reveal the risks of overusing or misusing fakes and how to mitigate them.
Fakes can differ from real components in subtle ways, causing tests to pass when real code would fail. For example, a fake database might not enforce constraints or handle concurrency. To avoid this, combine fakes with integration tests using real components. Also, keep fakes simple and well-maintained to reduce drift from reality.
Result
Learners become aware of the limits of fakes and how to use them wisely.
Understanding fakes' limitations helps maintain test reliability and catch real bugs.
Under the Hood
Fake objects work by implementing the same interface or class as the real component but replace complex operations with simple, fast code. They keep internal state in memory and return predictable results. During test execution, the test code interacts with the fake instead of the real component, isolating the test from external dependencies.
Why designed this way?
Fakes were designed to speed up tests and reduce flakiness by avoiding slow or unreliable real components. They provide a middle ground between no replacement and full mocks, offering working behavior without full complexity. This design balances test speed, reliability, and realism.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│  Test Code    │──────▶│  Fake Object  │──────▶│  Internal     │
│               │       │ (implements   │       │  In-memory    │
│               │       │  interface)   │       │  State & Logic│
└───────────────┘       └───────────────┘       └───────────────┘

Test code calls fake object methods, which run simple logic and store data internally.
Myth Busters - 4 Common Misconceptions
Quick: Do you think fake objects always verify method calls like mocks? Commit to yes or no.
Common Belief:Fake objects verify that certain methods were called during tests, just like mocks.
Tap to reveal reality
Reality:Fake objects provide working behavior but usually do not verify interactions; that is the role of mocks.
Why it matters:Confusing fakes with mocks can lead to missing important interaction checks or writing tests that don't verify behavior properly.
Quick: Do you think fake objects must implement every method of the real component? Commit to yes or no.
Common Belief:Fakes must implement all methods of the real component to be valid substitutes.
Tap to reveal reality
Reality:Fakes only need to implement the methods used in tests; unused methods can be left unimplemented or throw exceptions.
Why it matters:Trying to implement everything makes fakes complex and hard to maintain, defeating their purpose.
Quick: Do you think using fakes guarantees tests will catch all bugs in real components? Commit to yes or no.
Common Belief:Tests using fakes catch all bugs because fakes behave like real components.
Tap to reveal reality
Reality:Fakes simplify behavior and may miss bugs that only appear with real components, so integration tests are still needed.
Why it matters:Relying only on fakes can give false confidence and miss critical bugs.
Quick: Do you think fakes are always faster than real components? Commit to yes or no.
Common Belief:Fakes are always faster than real components in tests.
Tap to reveal reality
Reality:While usually faster, poorly designed fakes with complex logic can be slow and negate benefits.
Why it matters:Assuming all fakes are fast can lead to slow tests if fakes are not kept simple.
Expert Zone
1
Fakes should be designed to fail fast on unsupported operations to catch misuse early.
2
Maintaining fakes requires discipline to keep them aligned with real component behavior as systems evolve.
3
Using fakes in parallel tests requires careful handling of shared state to avoid flaky tests.
When NOT to use
Avoid fakes when the component behavior is too complex or critical, such as security checks or transaction management. Instead, use mocks for interaction verification or real components in integration tests to ensure correctness.
Production Patterns
In professional projects, fakes are often used for databases (in-memory stores), external services (simple simulators), and caches. They are combined with mocks for interaction checks and integration tests with real systems to cover all bases.
Connections
Mock objects
Related test doubles with different purposes; mocks verify interactions while fakes provide working behavior.
Understanding the difference helps choose the right tool for testing needs and improves test clarity.
Dependency Injection
Fakes are often injected into code under test to replace real dependencies during testing.
Knowing dependency injection enables easy swapping of real components with fakes, making tests flexible and isolated.
Simulation in Engineering
Fakes simulate real components in a controlled environment, similar to how engineers use simulators to test systems safely.
Recognizing this connection shows how testing borrows ideas from other fields to manage complexity and risk.
Common Pitfalls
#1Creating overly complex fakes that replicate full real component logic.
Wrong approach:public class ComplexFakeDatabase implements Database { // Implements all SQL features and transactions fully // Large codebase mimicking real DB }
Correct approach:public class SimpleFakeDatabase implements Database { private Map data = new HashMap<>(); @Override public void save(String key, String value) { data.put(key, value); } @Override public String find(String key) { return data.get(key); } }
Root cause:Misunderstanding that fakes should be simple leads to unnecessary complexity and maintenance burden.
#2Using fakes to verify method calls instead of mocks.
Wrong approach:FakeUserRepository fake = new FakeUserRepository(); // Trying to check if addUser was called by inspecting fake internals assertTrue(fake.wasAddUserCalled());
Correct approach:UserRepository mock = Mockito.mock(UserRepository.class); // Verify interaction with mock Mockito.verify(mock).addUser(any(User.class));
Root cause:Confusing the purpose of fakes and mocks causes improper test verification.
#3Not updating fakes when real components change, causing test drift.
Wrong approach:// Real component adds new method but fake is not updated // Tests pass but real code fails in production
Correct approach:// Update fake to implement new method or throw UnsupportedOperationException
Root cause:Neglecting maintenance of fakes leads to false positives in tests.
Key Takeaways
Fake objects are simple, working substitutes that speed up and isolate tests by replacing real components.
They differ from mocks and stubs by providing actual behavior, not just interaction verification or fixed responses.
Fakes should be kept simple and only implement methods needed for tests to avoid complexity and maintenance issues.
While fakes improve test speed and reliability, they cannot catch all bugs, so integration tests with real components remain essential.
Understanding when and how to use fakes helps write better tests and maintain confidence in software quality.