All topics
Testingbeginner

Unit Testing Fundamentals with Jest

The core building blocks of writing unit tests — test/describe blocks, assertions with expect, and the arrange-act-assert structure — using Jest as the representative framework.

A unit test states the setup, the action and the expectation.

Unit tests are like a series of small, automated inspections on an assembly line: instead of a human periodically eyeballing finished products for defects, a machine automatically checks a very specific measurement (one assertion) on every single unit that passes by, immediately flagging exactly which measurement failed and by how much, rather than a vague 'something seems off' after the fact.

Key Concepts

1
    describe('calculateTotal', () => {
      it('applies the bulk discount above 100 units', () => {
        // arrange
        const order = { quantity: 150, unitPrice: 10 };
2
        // act
        const total = calculateTotal(order);
3
        // assert
        expect(total).toBe(1350);
      });
    });
4
Arrange, act, assert is the structure worth keeping. A test where the three are tangled is hard to read when it fails.
5
The matchers you will use.
    toBe(x)               ===, for primitives
    toEqual(x)            deep equality, for objects
    toStrictEqual(x)      also checks undefined keys and class
    toContain(x)          array or string
    toThrow()             the function throws
    toHaveBeenCalledWith  a mock's arguments
    resolves / rejects    for promises
6
toBe against toEqual is the common mistake.
    expect({ a: 1 }).toBe({ a: 1 });      // fails -- different objects
    expect({ a: 1 }).toEqual({ a: 1 });   // passes
toBetoEqual
7
Name tests by behaviour, not by method.
    it('returns the total')                       // says nothing
    it('applies the bulk discount above 100')     // says what it does
8
When it fails, the name should tell you what broke without opening the file.
9
One assertion per behaviour. Several assertions about one behaviour are fine. A test asserting three unrelated things fails on the first and hides the rest.
10
Setup and teardown.
    beforeEach(() => { db = createTestDb(); });
    afterEach(() => { db.close(); });
11
beforeEach over beforeAll unless setup is expensive — shared state between tests is the most common cause of tests that pass alone and fail together.
beforeEachbeforeAll
12
Test the behaviour, not the implementation.
    expect(result.total).toBe(1350);                 // behaviour
    expect(service.calculateSubtotal).toHaveBeenCalled();  // internals
13
The second breaks when you refactor, which is exactly when you most want the test to keep working.
14
And the rule about coverage of the interesting cases. The boundary values — 99, 100, 101 — and the error paths matter more than the happy path, which is usually the one that already works.
15
What the interviewer is probing.1. "What makes a good unit test?" Probing: the principles. Stalls: "It passes." Moves up: it tests one behaviour through the public surface, is independent of other tests, and fails for exactly one reason.
16
2. "What should you not test?" Probing: judgement. Stalls: "Nothing is off limits." *Moves up:* implementation details and third-party code — asserting private calls makes refactoring break tests that should not care.
17
3. "What is the arrange-act-assert shape for?" Probing: structure. Stalls: "Style." *Moves up:* it keeps setup, action and verification separate so a reader sees what is being tested and what is incidental.
18
4. "Why does test independence matter?" Probing: the failure mode. Stalls: "It is tidy." Moves up: shared state makes failures depend on order, which produces intermittent failures nobody can reproduce.