Testing & TDD

Jest Basics: Modern JavaScript Testing

20 min Lesson 5 of 35

Introduction to Jest

Jest is a delightful JavaScript testing framework developed by Facebook, designed to work seamlessly with projects using React, Vue, Angular, Node.js, and more. It provides a zero-configuration testing experience with built-in code coverage, mocking capabilities, and an intuitive API that makes testing enjoyable rather than tedious.

Unlike older testing frameworks that required extensive setup and multiple libraries, Jest comes with everything you need out of the box: test runner, assertion library, mocking utilities, and coverage reports. It's fast, isolated, and provides excellent error messages that help you identify issues quickly.

Why Choose Jest?

Jest has become the de facto standard for JavaScript testing because it combines simplicity with power. Whether you're testing a small utility function or a complex React application, Jest provides the tools you need without overwhelming configuration.

Installation and Setup

Installing Jest

Installing Jest is straightforward using npm or yarn. For a Node.js project:

# Using npm npm install --save-dev jest # Using yarn yarn add --dev jest

For React projects created with Create React App, Jest comes pre-installed and configured. For other React projects:

npm install --save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-react

Basic Configuration

Add a test script to your package.json:

{ "scripts": { "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage" } }

For TypeScript projects, install additional dependencies:

npm install --save-dev @types/jest ts-jest

Create a jest.config.js file for custom configuration:

module.exports = { testEnvironment: 'node', coverageDirectory: 'coverage', collectCoverageFrom: [ 'src/**/*.js', '!src/**/*.test.js' ], testMatch: [ '**/__tests__/**/*.js', '**/?(*.)+(spec|test).js' ] };
Configuration Options:
  • testEnvironment: Use 'node' for backend, 'jsdom' for frontend
  • coverageDirectory: Where to output coverage reports
  • collectCoverageFrom: Which files to include in coverage
  • testMatch: Patterns to identify test files

Writing Your First Test

Simple Function Test

Let's create a simple function and test it. First, create sum.js:

function sum(a, b) { return a + b; } module.exports = sum;

Now create sum.test.js:

const sum = require('./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); test('adds -1 + 1 to equal 0', () => { expect(sum(-1, 1)).toBe(0); });

Run the tests with:

npm test
Test File Naming:

Jest automatically finds test files with these patterns: *.test.js, *.spec.js, or files in __tests__ folders. Use consistent naming across your project.

Test Structure: describe, it, and test

The test() Function

The test() function (alias it()) is the basic building block. It takes a description and a callback:

test('description of what you're testing', () => { // Test code here expect(actualValue).toBe(expectedValue); });

The describe() Block

describe() groups related tests together, creating a test suite:

describe('Math operations', () => { test('addition works correctly', () => { expect(2 + 2).toBe(4); }); test('subtraction works correctly', () => { expect(5 - 3).toBe(2); }); test('multiplication works correctly', () => { expect(3 * 4).toBe(12); }); });

Nested describe Blocks

You can nest describe blocks for better organization:

describe('Calculator', () => { describe('Addition', () => { test('adds positive numbers', () => { expect(5 + 3).toBe(8); }); test('adds negative numbers', () => { expect(-5 + -3).toBe(-8); }); }); describe('Subtraction', () => { test('subtracts positive numbers', () => { expect(5 - 3).toBe(2); }); test('subtracts negative numbers', () => { expect(-5 - -3).toBe(-2); }); }); });

Common Matchers

Equality Matchers

Jest provides various matchers for different comparison needs:

// Exact equality (for primitives) test('toBe matches exact values', () => { expect(2 + 2).toBe(4); expect('hello').toBe('hello'); }); // Deep equality (for objects and arrays) test('toEqual matches object structure', () => { const data = { name: 'John', age: 30 }; expect(data).toEqual({ name: 'John', age: 30 }); }); // Not equal test('not matchers', () => { expect(2 + 2).not.toBe(5); expect('hello').not.toBe('world'); });
toBe vs toEqual:

Use toBe() for primitive values (numbers, strings, booleans) and toEqual() for objects and arrays. Using toBe() with objects compares references, not values!

Truthiness Matchers

test('truthiness matchers', () => { expect(null).toBeNull(); expect(undefined).toBeUndefined(); expect(true).toBeTruthy(); expect(false).toBeFalsy(); expect(0).toBeFalsy(); expect('').toBeFalsy(); expect('hello').toBeTruthy(); });

Number Matchers

test('number matchers', () => { const value = 2 + 2; expect(value).toBeGreaterThan(3); expect(value).toBeGreaterThanOrEqual(4); expect(value).toBeLessThan(5); expect(value).toBeLessThanOrEqual(4); // Floating point equality expect(0.1 + 0.2).toBeCloseTo(0.3); });

String Matchers

test('string matchers', () => { expect('team').not.toMatch(/I/); expect('Christoph').toMatch(/stop/); expect('Hello World').toContain('World'); });

Array and Iterable Matchers

test('array matchers', () => { const shoppingList = ['apples', 'bananas', 'oranges']; expect(shoppingList).toContain('bananas'); expect(shoppingList).toHaveLength(3); expect(new Set(shoppingList)).toContain('apples'); });

The Test Runner

Running Tests

Jest provides several ways to run your tests:

# Run all tests npm test # Run tests in watch mode (re-runs on file changes) npm test -- --watch # Run tests with coverage npm test -- --coverage # Run a specific test file npm test sum.test.js # Run tests matching a pattern npm test -- --testNamePattern='addition'

Watch Mode

Watch mode is incredibly useful during development. It provides interactive options:

Watch Usage › Press f to run only failed tests. › Press o to only run tests related to changed files. › Press p to filter by a filename regex pattern. › Press t to filter by a test name regex pattern. › Press q to quit watch mode. › Press Enter to trigger a test run.
Watch Mode Best Practice:

Keep watch mode running while developing. It provides instant feedback and helps catch regressions immediately. Use the 'o' option to run only tests related to changed files for faster feedback.

Test Filtering

Jest allows you to skip or run only specific tests:

// Skip a test test.skip('this test will be skipped', () => { expect(true).toBe(false); }); // Run only this test test.only('only this test will run', () => { expect(true).toBe(true); }); // Skip an entire suite describe.skip('Suite to skip', () => { test('will not run', () => { expect(true).toBe(true); }); }); // Run only this suite describe.only('Only this suite', () => { test('will run', () => { expect(true).toBe(true); }); });
Don't Commit .only():

Never commit code with .only() modifiers. They prevent other tests from running in CI/CD pipelines. Use them only during local development.

Test Output and Reporting

Understanding Test Results

Jest provides clear, informative output:

PASS src/sum.test.js ✓ adds 1 + 2 to equal 3 (2 ms) ✓ adds -1 + 1 to equal 0 (1 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 1.234 s

Failure Messages

When tests fail, Jest shows detailed information:

FAIL src/sum.test.js ✕ adds 1 + 2 to equal 3 (5 ms) ● adds 1 + 2 to equal 3 expect(received).toBe(expected) // Object.is equality Expected: 3 Received: 4 2 | 3 | test('adds 1 + 2 to equal 3', () => { > 4 | expect(sum(1, 2)).toBe(3); | ^ 5 | });

Coverage Reports

Jest can generate comprehensive code coverage reports:

npm test -- --coverage

This generates a coverage report showing:

------------|---------|----------|---------|---------| File | % Stmts | % Branch | % Funcs | % Lines | ------------|---------|----------|---------|---------| All files | 100 | 100 | 100 | 100 | sum.js | 100 | 100 | 100 | 100 | ------------|---------|----------|---------|---------|
Coverage Metrics:
  • Statements: Percentage of code statements executed
  • Branches: Percentage of if/else branches tested
  • Functions: Percentage of functions called
  • Lines: Percentage of lines executed
Practice Exercise:
  1. Create a calculator.js module with add, subtract, multiply, and divide functions
  2. Write comprehensive tests in calculator.test.js using describe blocks to organize tests by operation
  3. Test edge cases like division by zero, negative numbers, and decimal calculations
  4. Run tests with coverage and aim for 100% coverage
  5. Use watch mode during development and experiment with filtering options

Best Practices

Test Organization

  • Group related tests with describe blocks
  • Use descriptive test names that explain what's being tested
  • Follow the Arrange-Act-Assert (AAA) pattern
  • Keep tests focused - one assertion per test when possible

Test Naming

// Good: Describes behavior clearly test('returns user name when user exists', () => { // ... }); // Bad: Vague description test('test user function', () => { // ... });

File Structure

Organize test files to mirror your source structure:

src/ utils/ calculator.js __tests__/ calculator.test.js components/ Button.jsx __tests__/ Button.test.jsx
Pro Tip:

Place test files close to the code they test. This makes it easier to find related tests and keeps your test organization aligned with your code structure.

Summary

In this lesson, we've covered the fundamentals of Jest, JavaScript's most popular testing framework. You've learned how to install and configure Jest, write tests using the test() and describe() functions, use common matchers for assertions, and leverage the powerful test runner for efficient development workflows.

Jest's zero-configuration approach, combined with features like watch mode, coverage reporting, and clear error messages, makes it an excellent choice for testing JavaScript applications of any size. As you continue learning, you'll discover more advanced features like mocking, snapshot testing, and async testing that make Jest even more powerful.