Jiaxi Liu (Jesse)

Master’s Graduate

Software Engineer | Scalable APIs · Web Scraping · Data Integration · Code Quality & Refactoring

Back to Blog

Jest and Supertest Review: Unit Tests, Mocks, Async Assertions, and API Integration Tests

Jest is useful for functions, service layers, React components, and async logic. Supertest is focused on HTTP API testing.

Jest Assertions

Common assertions:

expect(value).toBe(5);
expect(obj).toEqual({ id: 1 });
expect(value).toBeTruthy();
expect(arr).toContain("x");
expect(fn).toThrow(/error/);

For floating-point values, avoid direct equality.

expect(0.1 + 0.2).toBeCloseTo(0.3);

Unit Tests

Business function:

export function applyDiscount(price: number, discount: number) {
  if (discount < 0 || discount > 1) return price;
  return price * (1 - discount);
}

Tests:

test("calculates discount", () => {
  expect(applyDiscount(100, 0.2)).toBe(80);
});
 
test("returns original price for invalid discount", () => {
  expect(applyDiscount(100, 1.5)).toBe(100);
});

Mocking External Requests

Mocking isolates the current logic from external systems.

jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
 
mockedAxios.get.mockResolvedValueOnce({
  data: { id: 1, name: "Jesse" },
});
 
await expect(getUser(1)).resolves.toEqual({ id: 1, name: "Jesse" });
expect(mockedAxios.get).toHaveBeenCalledWith("/users/1");

Common mock targets:

  • Network requests
  • Database repositories
  • Time
  • Randomness
  • Third-party services

Async Tests

await expect(fetchUser()).resolves.toMatchObject({ id: 1 });
await expect(fetchUser()).rejects.toThrow();

Always return or await Promises in async tests.

API Testing with Supertest

Supertest can test Express, Next, or Nest APIs without binding a real port.

const res = await request(app)
  .get("/user/1")
  .query({ detail: "full" })
  .set("Authorization", "Bearer test123");
 
expect(res.statusCode).toBe(200);
expect(res.body).toHaveProperty("id", "1");

POST JSON:

await request(app)
  .post("/login")
  .send({ username: "jesse", password: "123456" })
  .expect(200);

API tests should check status codes, response shape, error branches, permissions, and boundary inputs.

Deeper Notes

When reviewing this topic, do not memorize names only. Focus on Jest assertions, mocks, async tests, Supertest API testing, and boundary cases. If this stays at the definition level, it becomes hard to explain in interviews or apply in projects. A stronger way to study it is to place it in a concrete scenario: who calls it, where the input comes from, what happens on failure, and whether data or state can be processed twice.

  • Testing is not about chasing coverage numbers; it is about locking down important behavior and risk boundaries cheaply.
  • Unit, integration, and E2E tests should have clear roles; UI-level tests should be fewer but critical.
  • Robust tests avoid real time, random data, network instability, and external service state.

In a real project, use it as a decision framework: identify inputs, constraints, failure modes, and observability before choosing a specific tool or pattern. If a solution looks simple, keep asking whether it still works when scale grows, permissions change, recovery matters, and more people collaborate on it.

Practical Checklist

  • Identify where this concept sits in the system: development-time constraint, runtime behavior, infrastructure capability, or collaboration workflow.
  • Write one minimal working example and one failure example; only knowing the happy path is usually not enough.
  • Record common misuses: edge cases, permission assumptions, performance assumptions, sync/async differences, or environment differences.
  • Connect the concept to a project experience so that an interview answer can be grounded in real tradeoffs.
  • End with one sentence about tradeoff: what it gives up and what it buys.

Self-Check Questions

  1. What core problem does this topic solve?
  2. What alternatives exist, and what are their costs?
  3. Where are the most likely edge cases?
  4. How would code, tests, or monitoring prove that it is reliable?

Applied Scenario

A login or order endpoint is a useful example. Unit tests verify pure functions and edge cases, integration tests verify API/database behavior, and E2E tests verify the critical user path. Mocks should isolate external dependencies, not hide real behavior. The closer a test is to the UI, the slower and more expensive it usually is, so UI-level tests should cover the most important paths.

Common Pitfalls:

  • Testing only the successful path.
  • Mocking so much that tests no longer reflect the real system.
  • Letting E2E tests depend on real time or unstable external services.