> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loremstock.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementing Unit Testing with Jest in Node.js

> Write and run unit tests for Node.js functions and Express routes using Jest and Supertest. Covers mocking mysql2, async tests, coverage reports, and TDD.

Unit testing ensures individual pieces of code work correctly in isolation. Writing tests catches bugs early, enables confident refactoring, and documents expected behavior. This page covers Jest from setup through advanced techniques including mocking `mysql2` and Supertest for Express route testing.

## What Is Unit Testing

A unit test tests the smallest piece of code in isolation — typically a single function or controller method. "Isolation" means dependencies (MySQL pool, other modules) are replaced with controlled fakes (mocks).

### The Testing Pyramid

```text theme={null}
         /\
        /E2E\           Few, slow, expensive
       /------\
      /Integration\     Some: test components together
     /------------\
    /  Unit Tests   \   Many, fast, cheap
   /________________\
```

## Install Jest and Supertest

```bash theme={null}
npm install --save-dev jest supertest
```

```json theme={null}
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "testEnvironment": "node"
  }
}
```

## Test File Naming

* `user.test.js`
* `auth.spec.js`
* `__tests__/user.js`

## Jest Basics

```javascript theme={null}
describe('Block name', () => {
  it('should do something', () => {
    expect(result).toBe(expected);
  });
});
```

### Common Matchers

| Matcher                     | Use Case                       |
| --------------------------- | ------------------------------ |
| `toBe(val)`                 | Primitive equality (`===`)     |
| `toEqual(val)`              | Deep object equality           |
| `toBeTruthy()`              | Value is truthy                |
| `toBeFalsy()`               | Value is falsy                 |
| `toContain(item)`           | Array or string contains item  |
| `toThrow(msg)`              | Function throws an error       |
| `toHaveBeenCalled()`        | Mock was called                |
| `toHaveBeenCalledWith(...)` | Mock called with specific args |
| `resolves.toBe(val)`        | Promise resolves to value      |
| `rejects.toThrow(...)`      | Promise rejects with error     |

## Testing a Pure Function

```javascript theme={null}
// utils/math.js
function add(a, b) { return a + b; }
function divide(a, b) {
  if (b === 0) throw new Error('Cannot divide by zero');
  return a / b;
}
module.exports = { add, divide };
```

```javascript theme={null}
// utils/math.test.js
const { add, divide } = require('./math');

describe('Math utilities', () => {
  it('adds two positive numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  it('throws on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
  });
});
```

## Mocking mysql2

When testing controllers, you mock the database pool so tests never touch a real MySQL server.

```javascript theme={null}
// Mock the entire config/db module
jest.mock('../config/db');
const pool = require('../config/db');
```

```javascript theme={null}
// controllers/userController.test.js
const { createUser, getUserById } = require('./userController');
const pool = require('../config/db');

jest.mock('../config/db');

describe('getUserById()', () => {
  it('returns the user when found', async () => {
    const mockUser = { id: 1, name: 'Alice', email: 'alice@test.com', role: 'user' };

    // pool.execute returns [rows, fields]; mock the resolved value
    pool.execute.mockResolvedValueOnce([[mockUser], []]);

    const req = { params: { id: '1' } };
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn()
    };

    await getUserById(req, res);

    expect(pool.execute).toHaveBeenCalledWith(
      expect.stringContaining('WHERE id = ?'),
      ['1']
    );
    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toHaveBeenCalledWith({ data: mockUser });
  });

  it('returns 404 when user not found', async () => {
    pool.execute.mockResolvedValueOnce([[], []]);

    const req = { params: { id: '999' } };
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn()
    };

    await getUserById(req, res);

    expect(res.status).toHaveBeenCalledWith(404);
  });
});
```

### Mocking Patterns Reference

| Pattern             | Code                                                            | Use Case          |
| ------------------- | --------------------------------------------------------------- | ----------------- |
| Mock resolved value | `pool.execute.mockResolvedValueOnce([[rows], []])`              | Successful SELECT |
| Mock empty result   | `pool.execute.mockResolvedValueOnce([[], []])`                  | Not found         |
| Mock INSERT result  | `pool.execute.mockResolvedValueOnce([{ insertId: 5 }, []])`     | Successful INSERT |
| Mock UPDATE result  | `pool.execute.mockResolvedValueOnce([{ affectedRows: 1 }, []])` | Successful UPDATE |
| Mock rejected value | `pool.execute.mockRejectedValueOnce(new Error('DB error'))`     | Database error    |

## Supertest: Testing Express Routes

Supertest sends real HTTP requests to your Express app without starting a server.

```javascript theme={null}
// app.js — export WITHOUT calling app.listen()
const express = require('express');
const app = express();
app.use(express.json());
app.use('/api/v1/users', require('./routes/userRoutes'));
module.exports = app;
```

```javascript theme={null}
// routes/users.test.js
const request = require('supertest');
const app = require('../app');
const pool = require('../config/db');

jest.mock('../config/db');

describe('POST /api/v1/users', () => {
  beforeEach(() => jest.clearAllMocks());

  it('creates a user and returns 201', async () => {
    // No duplicate found
    pool.execute
      .mockResolvedValueOnce([[], []])                          // SELECT (check duplicate)
      .mockResolvedValueOnce([{ insertId: 7 }, []]);           // INSERT

    const res = await request(app)
      .post('/api/v1/users')
      .send({ name: 'Alice', email: 'alice@test.com', password: 'password123' });

    expect(res.status).toBe(201);
    expect(res.body.data.email).toBe('alice@test.com');
    expect(res.body.data.password).toBeUndefined();
  });

  it('returns 400 when fields are missing', async () => {
    const res = await request(app)
      .post('/api/v1/users')
      .send({ name: 'Bob' });

    expect(res.status).toBe(400);
  });

  it('returns 409 when email already exists', async () => {
    pool.execute.mockResolvedValueOnce([[{ id: 1 }], []]);   // Duplicate found

    const res = await request(app)
      .post('/api/v1/users')
      .send({ name: 'Alice', email: 'alice@test.com', password: 'password123' });

    expect(res.status).toBe(409);
  });
});
```

## Setup and Teardown Hooks

```javascript theme={null}
let testToken;

beforeAll(() => {
  // Generate a token for protected route tests
  testToken = jwt.sign({ sub: 1, role: 'admin' }, process.env.JWT_SECRET);
});

beforeEach(() => {
  jest.clearAllMocks(); // Reset all mock call counts and return values
});
```

## Code Coverage

```bash theme={null}
jest --coverage
```

| Metric     | Meaning                      |
| ---------- | ---------------------------- |
| **Stmts**  | % of statements executed     |
| **Branch** | % of if/else branches tested |
| **Funcs**  | % of functions called        |
| **Lines**  | % of lines executed          |

Aim for 80%+ coverage on controller and service files.

## TDD: Test-Driven Development

<Steps>
  <Step title="Red">
    Write a failing test before writing the code. It fails because the function doesn't exist yet.
  </Step>

  <Step title="Green">
    Write the minimum code to make the test pass.
  </Step>

  <Step title="Refactor">
    Improve the code while keeping all tests green.
  </Step>
</Steps>

## Key Terms

| Term          | Definition                                                           |
| ------------- | -------------------------------------------------------------------- |
| **Unit test** | Test for a single function in isolation                              |
| **Mock**      | Fake replacement for a real dependency (database pool, external API) |
| **Stub**      | Simplified implementation returning a preset value                   |
| **Spy**       | Wrapper that records calls to a real function                        |
| **Assertion** | `expect(...)` statement that checks a condition                      |
| **Coverage**  | Percentage of code exercised by the test suite                       |
| **TDD**       | Test-Driven Development: write tests first, then code                |
| **Supertest** | Library for testing Express routes with real HTTP requests           |

## Common Mistakes

<Accordion title="Not exporting app without app.listen()">
  If app.js calls app.listen(), Supertest imports it and starts a real server. Export the app separately and have a server.js that calls app.listen(). Only import the app in tests.
</Accordion>

<Accordion title="Not clearing mocks between tests">
  Uncleaned mocks from one test leak into the next. Use jest.clearAllMocks() in beforeEach so each test starts fresh.
</Accordion>

<Accordion title="Mocking pool.execute with wrong return shape">
  mysql2's pool.execute returns \[rows, fields] as an array of two items. Your mock must return the same shape: mockResolvedValueOnce(\[\[rows], \[]]). A flat array \[rows] will make destructuring return the wrong values.
</Accordion>

<Accordion title="Testing implementation details">
  Tests should check what a function does (correct response, correct SQL called), not how it does it internally. Tests tied to internal logic break every refactor.
</Accordion>
