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.
Engineers test a brake caliper on a bench with simulated pressure, not by driving the whole car. Each part is verified alone before assembly.
When to use it
- Every domain class with rules.
- Before refactoring, to lock in behavior.
- To drive design (test-driven development).
Where it shows up in interviews
Recognize it when: 'how would you test this?'
- Test a parking fee calculator
- Test an elevator scheduler
- Test a rate limiter
Where it is used in real software
Standard unit test frameworks across languages.
Mockito creates mocks and stubs for Java collaborators.
Many fast unit tests, fewer integration tests, few end-to-end tests.
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.
How it works, step by step
- 1Test behavior, not implementation
Assert outcomes through public APIs.
- 2Inject collaborators
Use fakes for repositories, clocks, gateways.
- 3Cover edge cases
Boundaries, empty, full, invalid, concurrent.
- 4One behavior per test
Descriptive names: chargesTwoHoursForNinetyOneMinutes.
- 5Keep tests fast and deterministic
No sleeps, network, or real time.
Test cases for a parking fee calculator
$2/hour, first 15 minutes free, daily max $20
| Case | Duration | Expected fee |
|---|---|---|
| Within grace period | 10 min | $0 |
| Just after grace | 16 min | $2 |
| Exact hour boundary | 60 min | $2 |
| Partial hour rounds up | 61 min | $4 |
| Daily cap | 14 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.
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); });});Complexity and performance
Thousands per minute.
Boundaries and invalid input.
Trade-offs
Mocks verify interactions but couple tests to implementation; fakes test outcomes and survive refactoring better.
High coverage of trivial code adds little; focus on rules and edge cases.
Variants and related techniques
Generate random inputs to check invariants (fast-check, jqwik).
Shared tests every implementation of an interface must pass.
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.
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.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Write boundary tests for a discount engine | Easy | Edge cases. |
| Test an elevator scheduler with a fake clock | Medium | Determinism. |