SwadhyAI Notes

All Low-Level Design notes

Production LLD

Stub, fake, mock

You are writing a parking-lot exit test whose only hard rule is "should not send a real SMS." Someone still types Mockito.mock(Everything.class) (Mockito is a Java mocking library) and calls that a unit test. The suite goes green, then the next harmless query breaks it, or a real message leaves the machine because nobody asserted the send.

A test double stands in for a real collaborator (any object the unit talks to) so the test can run in memory., , and are different tools. Using the wrong one makes tests brittle or makes them lie.

Which double?

1

Unused constructor argument so the test still compiles.

2

Canned return when you only need a value.

3

In-memory stand-in that honors the real port.

4

Records calls while delegating to a real or fake object.

5

Assert the call that must not leak.

What actually happens when the double is wrong

The SMS story above is the default when every collaborator is labelled "a mock." Mockito will happily stub a HashMap, a Money value object (a small immutable type), or a fluent query you do not care about. The test then encodes call order instead of the behaviour you wanted: one notification, a closed ticket, noon on the clock.

Two failure modes show up. Brittle: you asserted every interaction, so the next harmless find fails the suite (see the over-mock sample below). Lying: you stubbed the sender and never checked it, so a real SMS path can still run. The fix is not more Mockito. It is picking the double that matches the question you are asking.

Ask one question: do I need a canned return, an in-memory stand-in for a port (the interface production code depends on), or proof that a call happened? That question picks the double. Name it , , or ; stop calling every collaborator a mock.

Simple rule of thumb

Once you have that question, the decision path is mechanical. Each row is one situation from the parking-lot use case that the code sample below implements.

I only need Instant.now to be noon

Stub a Clock (or Clock.fixed).

The use case save() then find()

Fake the repository with a HashMap.

A real SMS must not leave the machine

Spy or mock the NotificationChannel and assert send count.

The constructor wants a listener I never use

Dummy / no-op implementation.

I am about to mock HashMap or Money

Stop. Use the real value object or write a twelve-line fake.

Rule of thumb

a value, a store, a side effect (SMS, charge, email). Prefer the HashMap repository and recording channel over mocking everything.

Fake store, stub clock, recording sender

The path above in Java: a TicketRepository, a Clock, and a . No Mockito required for this exit test.

java
class InMemoryTicketRepository implements TicketRepository {
  private final Map<TicketId, Ticket> data = new HashMap<>();

  public void save(Ticket t) { data.put(t.id(), t); }

  public Optional<Ticket> find(TicketId id) {
    return Optional.ofNullable(data.get(id));
  }

  public Ticket require(TicketId id) {
    return find(id).orElseThrow(() -> new UnknownTicketException(id));
  }
}

class NoonClock extends Clock {
  private static final Instant NOON = Instant.parse("2026-01-15T12:00:00Z");
  public Instant instant() { return NOON; }
  public ZoneId getZone() { return ZoneOffset.UTC; }
  public Clock withZone(ZoneId zone) { return this; }
}

class RecordingChannel implements NotificationChannel {
  final List<Notification> sent = new ArrayList<>();
  public void send(Notification n) { sent.add(n); }
}

@Test
void exitNotifiesOnce() {
  InMemoryTicketRepository tickets = new InMemoryTicketRepository();
  RecordingChannel channel = new RecordingChannel();
  ParkingService service = new ParkingService(new NoonClock(), sequentialIds(), nearest(), hourly(), tickets, channel);
  Ticket ticket = service.park(car("KA-01"));
  service.exit(ticket.id());
  assertEquals(1, channel.sent.size());
  assertEquals(ticket.id(), channel.sent.get(0).ticketId());
}

Worked parking-lot trio

Same use case as the sample, written as a roster so you can see which collaborator is which double and whether you assert on it. Mixing those roles (mocking the map, stubbing the sender and never asserting) is how tests lie.

CollaboratorDoubleAssert on it?Why
ClockStub / Clock.fixedNoYou need noon, not a call count.
IdGeneratorStub sequentialOptionalStable T-1 is enough.
TicketRepositoryFake HashMapOutcomeTicket is there and CLOSED.
PricingStrategyReal HourlyPricingNoCheap and deterministic with Instant.
NotificationChannelSpy / RecordingChannelYesMust not SMS; must send once.
MoneyReal value objectYes amountNever mock a value object.

Do not mock these

Money, TicketId, HashMap, and a List of spots are not ports. Value objects are cheap and honest. Collections are not ports. Mock ports you own; use real values.

Fakes honor invariants

An InMemoryTicketRepository that silently overwrites or accepts a null Ticket is not a fake; it is a liar. The fake should reject the same illegal states as production so tests of the God-object split (one class that did every job) still mean something.

java
public void save(Ticket t) {
  Objects.requireNonNull(t, "ticket");
  data.put(t.id(), t);
}

When the mock is honest, and when it is not

The roster says assert the channel and the ticket outcome, not every repository call. Here is the brittle version next to the cases where a is the honest tool.

Brittle interaction test

Asserting every interaction couples you to call order. The next harmless query breaks the suite.

java
@Test
void brittle() {
  TicketRepository repo = Mockito.mock(TicketRepository.class);
  when(repo.find(any())).thenReturn(Optional.of(ticket));
  verify(repo, times(1)).save(any());
  verifyNoMoreInteractions(repo); // next harmless query fails the test
}

When a mock is honest

  • The collaborator has a side effect you must not perform (SMS, charge, email).
  • The requirement is the interaction itself (notify once, not twice).
  • You still assert outcomes on the fake store: the ticket is CLOSED after exit.

How to explain this in an interview

You have the roster and the brittle counterexample. In the room you rarely need the full catalog. Say "in-memory ticket store" and "fixed clock." That sentence is more precise than a mocking framework name.

A clean spoken answer

"I would fake TicketRepository with a map, stub Clock at noon, and record NotificationChannel so we never send SMS. I would not mock Money."

Common mistakes

Mocking the class under test's data structures. Mockito.mock(HashMap.class) to stand in for a repository. Write a fake. You are testing your design, not a stubbing DSL (the when/verify language).

Asserting every interaction. verify(repo, times(1)).save(...) plus verifyNoMoreInteractions. Assert outcomes; mock only the side effect that must not leak (see the brittle sample above).

Mocking value objects. Money and TicketId should construct. A mock Money that returns 10 from every method hides primitive obsession (raw numbers where a type belongs).

Wrap-up

You have seen the wrong double, the decision path, and the parking-lot trio. Use these checks, then follow the related notes.

Check yourself

  1. For a rate limiter test, is Clock a stub or a fake? Why?
  2. Write a twelve-line InMemoryTicketRepository. What methods does the port actually need?
  3. When is RecordingChannel better than Mockito.mock(NotificationChannel.class)?

Takeaway

Doubles are a design vocabulary, not a framework feature. Pick the tool that matches the question, then keep the fake honest about the same illegal states production rejects.