IMPLEMENTATION QUALITY / OBJECT DESIGN BRIEF

Unit testing object models

Unit testing object models means verifying behavior of classes in isolation: given a state and an action, assert the outcome.

IntermediatePhase 07 / Topic 5 of 8ResponsibilitiesCollaborationsExtensibility
01

Overview

Unit testing object models means verifying behavior of classes in isolation: given a state and an action, assert the outcome. Good tests follow Arrange-Act-Assert, test behavior through public methods rather than private internals, use test doubles (fakes, stubs, mocks) for collaborators, and run in milliseconds.

Testability is a design signal. Classes that need a database, network, current time, or randomness to test have hidden dependencies; dependency injection, value objects, and pure domain logic make testing trivial. In LLD interviews, mentioning how you would test key classes, especially edge cases and concurrency, shows senior-level thinking.

Testing car parts on a bench

Engineers test a brake caliper on a bench with simulated pressure, not by driving the whole car. Each part is verified alone before assembly.

02

When to use it

  • Every domain class with rules.
  • Before refactoring, to lock in behavior.
  • To drive design (test-driven development).
03

Where it shows up in interviews

Testable design discussion

Recognize it when: 'how would you test this?'

  • Test a parking fee calculator
  • Test an elevator scheduler
  • Test a rate limiter
04

Where it is used in real software

JUnit 5, Jest, Vitest, pytest

Standard unit test frameworks across languages.

Mockito and test doubles

Mockito creates mocks and stubs for Java collaborators.

Test pyramid

Many fast unit tests, fewer integration tests, few end-to-end tests.

05

Key terms

Arrange-Act-Assert
Setup, perform the action, verify.
Fake
Working lightweight implementation (in-memory repository).
Stub
Returns canned answers.
Mock
Verifies interactions (was send called?).
Deterministic test
Same result every run: control time and randomness.
06

How it works, step by step

  1. 1
    Test behavior, not implementation

    Assert outcomes through public APIs.

  2. 2
    Inject collaborators

    Use fakes for repositories, clocks, gateways.

  3. 3
    Cover edge cases

    Boundaries, empty, full, invalid, concurrent.

  4. 4
    One behavior per test

    Descriptive names: chargesTwoHoursForNinetyOneMinutes.

  5. 5
    Keep tests fast and deterministic

    No sleeps, network, or real time.

07

Test cases for a parking fee calculator

$2/hour, first 15 minutes free, daily max $20

Step 1 / 5
CaseDurationExpected fee
Within grace period10 min$0
Just after grace16 min$2
Exact hour boundary60 min$2
Partial hour rounds up61 min$4
Daily cap14 hours$20

NOWCase: Within grace period | Duration: 10 min | Expected fee: $0

Boundary cases catch most bugs; a fake clock makes them trivial to write.

08

Implementation

import { describe, expect, it } from "vitest"; class FeeCalculator {  constructor(private perHourCents = 200, private graceMinutes = 15, private dailyCapCents = 2000) {}  fee(minutes: number) {    if (minutes <= this.graceMinutes) return 0;    return Math.min(Math.ceil(minutes / 60) * this.perHourCents, this.dailyCapCents);  }} describe("FeeCalculator", () => {  const calc = new FeeCalculator();  it.each([    [10, 0],    [16, 200],    [60, 200],    [61, 400],    [14 * 60, 2000],  ])("%i minutes costs %i cents", (minutes, expected) => {    expect(calc.fee(minutes)).toBe(expected);  });});
09

Complexity and performance

Unit test runtimeMilliseconds

Thousands per minute.

Edge cases per rule3-6

Boundaries and invalid input.

10

Trade-offs

Mocks vs fakes

Mocks verify interactions but couple tests to implementation; fakes test outcomes and survive refactoring better.

Coverage vs value

High coverage of trivial code adds little; focus on rules and edge cases.

11

Variants and related techniques

Property-based testing

Generate random inputs to check invariants (fast-check, jqwik).

Contract tests

Shared tests every implementation of an interface must pass.

12

Common mistakes

  • Testing private methods.

    Fix: Test through public behavior; extract a class if a private method needs its own tests.

  • Using real time and sleeps.

    Fix: Inject a clock and control it.

  • Over-mocking.

    Fix: Mock only boundaries; use real value objects and entities.

13

Interview questions

How would you test a rate limiter?

Inject a controllable clock, then test: requests within capacity succeed, the next one is rejected, tokens refill after the right time, independent clients do not affect each other, and concurrent requests never exceed the limit.

Mock vs stub vs fake?

A stub returns canned values; a mock also verifies it was called in specific ways; a fake is a simple working implementation such as an in-memory repository.

14

Practice problems

ProblemDifficultyWhat it trains
Write boundary tests for a discount engineEasyEdge cases.
Test an elevator scheduler with a fake clockMediumDeterminism.