Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Stubs, Fakes, and Mocks in Java

Posted on April 1, 2015 By Luis Fernandez

Stubs, fakes, and mocks are the unsung helpers behind green bars in JUnit. They keep tests fast, focused, and fearless. If your tests feel slow or fragile, odds are your doubles are doing the wrong job.

Java 8 is rolling across teams, JUnit 4 is still the default, and Mockito is everywhere. With all that, the basics still decide if your tests stick or break. Let’s keep it practical and grounded.

Why do test doubles matter in Java JUnit projects?

Because units need isolation. If your service pulls rates from an API, hits a database, or sends mail, a true unit test should not do any of that. It should run in memory and finish in milliseconds.

That is where stubs, fakes, and mocks step in. Same idea, different jobs. Get the names right, and your tests become simpler by default.

What is a stub?

A stub is a simple stand in that returns fixed data. It does not have logic or memory. It is there to feed your unit a known value so you can assert the outcome.

public interface ExchangeRateClient {
    BigDecimal rate(String from, String to);
}

public class MoneyConverter {
    private final ExchangeRateClient client;
    public MoneyConverter(ExchangeRateClient client) { this.client = client; }
    public BigDecimal convert(BigDecimal amount, String from, String to) {
        return amount.multiply(client.rate(from, to));
    }
}

public class StubExchangeRateClient implements ExchangeRateClient {
    @Override
    public BigDecimal rate(String from, String to) {
        return new BigDecimal("0.74"); // fixed value for test
    }
}

import org.junit.Test;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;

public class MoneyConverterTest {
    @Test
    public void convertsUsingStubbedRate() {
        MoneyConverter c = new MoneyConverter(new StubExchangeRateClient());
        BigDecimal eur = c.convert(new BigDecimal("100"), "USD", "EUR");
        assertEquals(new BigDecimal("74.00"), eur.setScale(2));
    }
}

Use a stub when the dependency is a data source and you only care about a known return value.

When should I use a fake?

A fake is a lightweight working version. It has simple logic or an in memory store. It behaves close to the real thing without network or disk.

public class User {
    private final String id;
    private final String email;
    public User(String id, String email) { this.id = id; this.email = email; }
    public String id() { return id; }
    public String email() { return email; }
}

public interface UserRepository {
    void save(User user);
    User findById(String id);
}

import java.util.HashMap;
import java.util.Map;

public class FakeUserRepository implements UserRepository {
    private final Map<String, User> data = new HashMap<>();
    @Override public void save(User user) { data.put(user.id(), user); }
    @Override public User findById(String id) { return data.get(id); }
}

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class UserServiceTest {
    @Test
    public void createsAndLoadsUserWithFakeRepo() {
        FakeUserRepository repo = new FakeUserRepository();
        repo.save(new User("42", "neo@matrix.io"));
        assertEquals("neo@matrix.io", repo.findById("42").email());
    }
}

Pick a fake when the dependency is a store or cache and you want realistic behavior with zero setup cost.

When do mocks fit?

A mock is about behavior. You set expectations and verify calls. This is handy when the output is not the point, but the interaction is.

public interface EmailGateway {
    void send(String to, String subject);
}

public class SignupService {
    private final EmailGateway email;
    public SignupService(EmailGateway email) { this.email = email; }
    public void signup(String emailAddress) {
        // ... create account ...
        email.send(emailAddress, "Welcome");
    }
}

import org.junit.Test;
import static org.mockito.Mockito.*;

public class SignupServiceTest {
    @Test
    public void sendsWelcomeEmailOnSignup() {
        EmailGateway gateway = mock(EmailGateway.class);
        SignupService service = new SignupService(gateway);

        service.signup("alice@example.com");

        verify(gateway).send("alice@example.com", "Welcome");
        verifyNoMoreInteractions(gateway);
    }
}

Keep mocks focused on observable interactions. Do not verify every tiny call. Verify the calls that matter to business rules.

How do I pick the right test double?

  • Stub when you need a fixed return value.
  • Fake when you need simple behavior that feels real.
  • Mock when the goal is to assert a call was made.
  • No double when the real thing is fast and in memory.

What about Spring, databases, and HTTP?

Do not spin the full context for a unit. Wire services by hand in tests. For controllers and repositories, write a few integration tests that boot the app and hit real boundaries, then keep the rest as pure unit tests with doubles.

How do I write readable JUnit tests?

import org.junit.Test;
import static org.junit.Assert.*;

public class PriceCalculatorTest {
    @Test
    public void totalIncludesTax() {
        // given
        TaxService tax = amount -> new BigDecimal("0.10"); // Java 8 lambda for a tiny stub
        PriceCalculator calc = new PriceCalculator(tax);

        // when
        BigDecimal total = calc.total(new BigDecimal("50.00"));

        // then
        assertEquals(new BigDecimal("55.00"), total);
    }
}

interface TaxService { BigDecimal rate(BigDecimal amount); }

class PriceCalculator {
    private final TaxService tax;
    PriceCalculator(TaxService tax) { this.tax = tax; }
    BigDecimal total(BigDecimal base) {
        BigDecimal rate = tax.rate(base);
        return base.add(base.multiply(rate));
    }
}

Arrange Act Assert is still king. Keep one reason to fail per test. Name tests like a sentence that explains the rule.

What are common smells?

  • Overmocking: verifying private chatter inside the unit.
  • Slow tests: network calls leaking into unit suites.
  • Brittle stubs: many tests break after a tiny signature change.
  • Hidden global state: singletons that remember things across tests.

Which tools should I reach for today?

JUnit 4 keeps things simple. Mockito covers most mocking needs and its syntax reads well. Hamcrest or plain JUnit assertions are fine. If you write Groovy tests, Spock is lovely for specs. For hard static stuff, some folks use PowerMock, but reach for that only when design is boxed in.

Quick cheat sheet

  • Business rule result test: use stubs or a fake.
  • Side effect test like email or queue: use a mock and verify.
  • Repository logic test: use a fake in memory repo.
  • End to end path: write a small set of integration tests and keep them separate.

Takeaway

Pick the simplest double that proves the rule. Stub for data, fake for behavior, mock for interaction. Keep the unit tight and fast, and push the heavy lifting to a small set of broader tests.

Your future self will thank you when the build stays quick, the diff is small, and the bar stays green.

Software Architecture Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes