Command Palette

Search for a command to run...

PHASE 3Intermediate ~13 min· topic 6 of 7

Topic 3.6

Testing Your LLD

In one line

A design is only proven when tests show it works and stays easy to change. A few sharp unit tests for the rules and edge cases are what separate 'it compiles' from 'I'd ship this'.

0/7 · 0%

Think of it like this

A car's crash test. You don't trust a car because it looks strong; you trust it because it was tested in the exact situations that go wrong. Tests are crash tests for your rules.

Key ideas

  1. 01

    Test the rules, not the getters: 'the lot rejects a truck when only small spots are free' is a good test; 'getName returns the name' is noise.

  2. 02

    Arrange → Act → Assert: set up the world, do one action, check one outcome. One behaviour per test, with a name that reads like a sentence.

  3. 03

    Always cover the edge cases you listed in requirements: full lot, duplicate entry, lost ticket, zero or negative amounts, boundary times (exactly 30 minutes).

  4. 04

    Inject time. Never call System.currentTimeMillis() inside logic; pass a java.time.Clock so tests can freeze or move time ('the car stayed 2h 01m').

  5. 05

    Fakes over mocks for your own ports: an InMemoryTicketRepository is simpler and more honest than a chain of when(...).thenReturn(...).

  6. 06

    Concurrency test: start N threads at the same moment with a CountDownLatch, all trying to take the last spot, then assert that exactly one succeeded.

  7. 07

    In an interview, even writing 3–4 test names out loud ('shouldRejectEntryWhenFull', 'shouldChargeDailyMaxForLostTicket') shows production thinking.

Java / Spring map

  • →

    JUnit 5 + AssertJ for readable assertions; Mockito only for third-party boundaries you don't own.

  • →

    java.time.Clock.fixed(...) and Clock.offset(...) make time-based rules (fines, expiries, pricing) fully testable.

Code & diagrams

ParkingLotTest.javajava

Rule tests, a time-based test with an injected Clock, and a real concurrency test.

class ParkingLotTest {

  @Test
  void rejectsTruckWhenOnlySmallSpotsFree() {
    ParkingLot lot = new ParkingLot(List.of(new Spot("S1", SpotSize.SMALL)));
    assertThatThrownBy(() -> lot.park(new Vehicle("KA01", VehicleType.TRUCK)))
        .isInstanceOf(NoSpotAvailableException.class);
  }

  @Test
  void firstThirtyMinutesAreFree() {
    MutableClock clock = new MutableClock(Instant.parse("2026-01-01T10:00:00Z"));
    ParkingService svc = new ParkingService(lotWithOneCarSpot(), new HourlyPricing(), clock);

    Ticket t = svc.enter(new Vehicle("KA01", VehicleType.CAR));
    clock.advance(Duration.ofMinutes(30));

    assertThat(svc.exit(t.id()).amount()).isEqualTo(Money.zero());
  }

  @Test
  void onlyOneOfTenGatesGetsTheLastSpot() throws Exception {
    ParkingLot lot = new ParkingLot(List.of(new Spot("M1", SpotSize.MEDIUM)));
    int gates = 10;
    CountDownLatch start = new CountDownLatch(1);
    AtomicInteger wins = new AtomicInteger();
    ExecutorService pool = Executors.newFixedThreadPool(gates);

    for (int i = 0; i < gates; i++) {
      int n = i;
      pool.submit(() -> {
        start.await();                                      // everyone starts together
        try { lot.park(new Vehicle("CAR" + n, VehicleType.CAR)); wins.incrementAndGet(); }
        catch (NoSpotAvailableException ignored) {}
        return null;
      });
    }
    start.countDown();
    pool.shutdown();
    pool.awaitTermination(5, TimeUnit.SECONDS);

    assertThat(wins.get()).isEqualTo(1);                    // never two cars in one spot
  }
}

Explain without notes

01

Why should business logic take a Clock instead of calling the system time directly?

02

What exactly does the CountDownLatch do in the concurrency test, and what would the test miss without it?

Practice

01

Write 5 test names (no bodies) for the Vending Machine and 5 for Splitwise. Cover at least two edge cases each.

02

Implement the 'last spot' concurrency test against your own Parking Lot and make it pass.

Trade-offs

  • ↔

    Mocks make tests fast to write but tie them to implementation details; fakes take a little longer to build but survive refactors.

Completion checklist

  • My designs take a Clock and interfaces, so every rule is testable without Spring or a database.

  • I can write a concurrency test that proves no double booking.

Back to phase