Bird
Raised Fist0
JUnittesting~15 mins

Fake objects in JUnit - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What is the main purpose of using a fake object in JUnit testing?
easy
A. To replace a real component with a simple version for controlled testing
B. To speed up the compilation of test classes
C. To generate random test data automatically
D. To log test execution details to a file

Solution

  1. Step 1: Understand the role of fake objects

    Fake objects are simplified versions of real components used in tests to isolate the code under test.
  2. Step 2: Identify the main purpose

    The main purpose is to replace real parts that might be slow, unavailable, or unpredictable with controlled versions.
  3. Final Answer:

    To replace a real component with a simple version for controlled testing -> Option A
  4. Quick Check:

    Fake objects replace real parts for testing [OK]
Hint: Fake objects replace real parts with simple versions [OK]
Common Mistakes:
  • Confusing fake objects with mocks or stubs
  • Thinking fake objects generate random data
  • Assuming fake objects speed up compilation
2. Which of the following is the correct way to define a fake object class in Java implementing an interface DataService?
easy
A. public class FakeDataService { int getData() { return 42; } }
B. public class FakeDataService extends DataService { public int getData() { return 42; } }
C. public class FakeDataService implements DataService { public int getData() { return 42; } }
D. public interface FakeDataService implements DataService { int getData(); }

Solution

  1. Step 1: Recall Java syntax for implementing interfaces

    In Java, a class implements an interface using the implements keyword and must provide method implementations.
  2. Step 2: Check each option

    public class FakeDataService implements DataService { public int getData() { return 42; } } correctly uses implements and defines the method. public class FakeDataService extends DataService { public int getData() { return 42; } } incorrectly uses extends with an interface. public interface FakeDataService implements DataService { int getData(); } wrongly declares an interface implementing another interface with method body. public class FakeDataService { int getData() { return 42; } } lacks interface implementation.
  3. Final Answer:

    public class FakeDataService implements DataService { public int getData() { return 42; } } -> Option C
  4. Quick Check:

    Class implements interface with 'implements' keyword [OK]
Hint: Use 'implements' keyword to define fake class for interface [OK]
Common Mistakes:
  • Using 'extends' instead of 'implements' for interfaces
  • Defining interface instead of class for fake
  • Omitting method implementation in fake class
3. Given the following fake object and test code, what will be the output of the assertion?
interface Calculator { int add(int a, int b); }
class FakeCalculator implements Calculator {
  public int add(int a, int b) { return 10; }
}

@Test
void testAdd() {
  Calculator calc = new FakeCalculator();
  assertEquals(10, calc.add(3, 7));
}
medium
A. Runtime exception due to wrong method signature
B. Test fails because 3 + 7 is 10
C. Compilation error due to missing override annotation
D. Test passes because fake returns fixed 10

Solution

  1. Step 1: Analyze the fake object's method

    The add method in FakeCalculator always returns 10 regardless of inputs.
  2. Step 2: Check the assertion in the test

    The test asserts that calc.add(3, 7) equals 10, which matches the fake's return value.
  3. Final Answer:

    Test passes because fake returns fixed 10 -> Option D
  4. Quick Check:

    Fake returns fixed value matching assertion [OK]
Hint: Fake returns fixed value; assertion matches it [OK]
Common Mistakes:
  • Expecting real addition instead of fake's fixed return
  • Confusing compilation errors with missing annotations
  • Assuming runtime exceptions without cause
4. Identify the error in this fake object implementation:
interface UserRepository {
  String findUserName(int id);
}

class FakeUserRepository implements UserRepository {
  public String findUserName() {
    return "TestUser";
  }
}
medium
A. Method signature does not match interface method
B. Class should be abstract to implement interface
C. Return type String is incorrect
D. Interface cannot be implemented by a class

Solution

  1. Step 1: Compare method signatures

    The interface method findUserName requires an int id parameter, but the fake method has no parameters.
  2. Step 2: Understand Java interface implementation rules

    Implementing classes must match method signatures exactly, including parameters.
  3. Final Answer:

    Method signature does not match interface method -> Option A
  4. Quick Check:

    Method signatures must match interface exactly [OK]
Hint: Check method parameters match interface exactly [OK]
Common Mistakes:
  • Ignoring method parameters in fake implementation
  • Thinking return type mismatch is the error
  • Believing class must be abstract to implement interface
5. You want to test a service that depends on a slow external API. Which approach best uses a fake object to improve test speed and reliability?
hard
A. Use the real API client but add delays in the test to wait for responses
B. Create a fake API client implementing the same interface that returns fixed test data instantly
C. Mock the API client to throw exceptions randomly during tests
D. Remove the API client dependency from the service code

Solution

  1. Step 1: Identify the problem with the real API

    The real API is slow and unreliable for tests, causing delays and flakiness.
  2. Step 2: Choose a fake object approach

    Creating a fake client that implements the same interface and returns fixed data instantly isolates the service and speeds tests.
  3. Step 3: Evaluate other options

    Use the real API client but add delays in the test to wait for responses slows tests further. Mock the API client to throw exceptions randomly during tests adds randomness, reducing reliability. Remove the API client dependency from the service code breaks service functionality.
  4. Final Answer:

    Create a fake API client implementing the same interface that returns fixed test data instantly -> Option B
  5. Quick Check:

    Fake replaces slow API with fast fixed data [OK]
Hint: Fake slow dependencies with fast fixed-return objects [OK]
Common Mistakes:
  • Adding delays instead of faking slow parts
  • Using mocks to add randomness in tests
  • Removing dependencies instead of faking them
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard