Skip to the content.

Jest

Jest is a delightful JavaScript Testing Framework with a focus on simplicity

yarn add --dev jest @types/jest ts-jest

Installs jest, types and wiring with typescript

Sample:

export const sum = (a: number, b: number): number => a + b;
//
import { sum } from "./sum";
// `test`
test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});
// `it`
it("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});

Using describe to group tests

describe(".asyncSum", () => {
  describe("success cases", () => {
    it("works with dummy payload", () => {});
    it("works with broken data", () => {});
  });
});

Using async in tests

export const asyncSum = (a: number, b: number): Promise<number> =>
  Promise.resolve(a + b);

it("works with async", async () => {
  const result = await asyncSum(1, 2);
  expect(result).toBe(3);
});

no need to put async in describe sections

Using callbacks in tests

test("the data is peanut butter", (done) => {
  function callback(data) {
    try {
      expect(data).toBe("peanut butter");
      done();
    } catch (error) {
      done(error);
    }
  }
  fetchData(callback);
});

Using Promise in tests

Better to use async approach and await for promise result and check it outside ot .then

test("the data is peanut butter", () => {
  return fetchData().then((data) => {
    expect(data).toBe("peanut butter");
  });
});

Running only some tests out of all in a file:

my-test.test.ts

describe(".asyncSum", () => {
  it("works with dummy payload", () => {});
  it("works with broken data", () => {});
  it.only("need to verify this", () => {});
  it.only("and this", () => {});
  it("skipped as well", () => {});
});

Execute only one file tests. And only .only tests will be running, all others will be skipped

jest -i my-test.test.ts

Using generated tests

test.each([
  [1, 1, 2],
  [1, 2, 3],
  [2, 1, 3],
])(".add(%i, %i)", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

Before and After

beforeAll(() => {});
afterAll(() => {});
describe("scoped setups", () => {
  beforeAll(() => {});
  afterAll(() => {});
});
// per test
beforeEach(() => {});
afterEach(() => {});

Using async is fine also:

afterAll(async () => {
  await cleanupDatabase();
  console.log("Database is removed");
});

configuration

Config file name jest.config.js, or any other passed in cli with --config.

Configuration can also be provided in package.json

{
  "jest": {
    "preset": "...",
    "globals": {
      "ts-jest": {
        "tsConfig": "tsconfig.json"
      }
    }
  }
}

There are many possible configurations https://jestjs.io/docs/en/configuration

expectations

There are many ways to match result with expected value https://jestjs.io/docs/en/expect

Most commonly used

test("dummy test", () => {
  // Comparing sample values like number, boolean, string
  expect(1).toBe(1);
  // `not` cases available in any expectation
  expect(1).not.toBe(2);
  // Comparing array length
  expect([1, 2, 3]).toHaveLength(3);
  // Something is defined
  const value: number | undefined = 1;
  expect(value).toBeDefined();
  // opposite to defined
  expect(value).not.toBeUndefined();
  // Deep comparing
  const object = {
    a: 10,
    b: [1, 2, 3],
  };
  expect(object).toEqual(object);
  // regex
  expect("car").toMatch(/.*/);
  // throws
  expect(() => {
    throw new Error(`Bam!`);
  }).toThrow();
});

mocks

It is useful to mock modules and provide dummy expected implementations without external network communications

Topic itself is complex and I will recommend to read docs:

jest.fn function mock

const mockCallback = jest.fn((x) => 42 + x);
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

Mock returned values

const myMock = jest.fn();

myMock.mockReturnValueOnce(10).mockReturnValueOnce("x").mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());

Mocking module fully

import * as uuid from "uuid";

jest.mock("uuid", () => ({
  v4: () => "some-uuid",
}));

uuid.v4(); // => some-uuid

Capture some values by mock function

import { sendDistributionMetric } from "datadog-lambda-js";
const metrics: any[] = [];

jest.mock("datadog-lambda-js", () => ({
  sendDistributionMetric: (...args: any) => {
    metrics.push(args);
  },
}));
sendDistributionMetric("my-metric", 1);

expect(metrics).toEqual([["my-metric", 1]]);

Checking mocks been called

import { trackRecordPushed, trackRecordRejected } from "./metrics";

jest.mock("./metrics", () => ({
  trackRecordPushed: jest.fn(),
  trackRecordRejected: jest.fn(),
}));

const trackRecordPushedMocked = trackRecordPushed as jest.Mock;
const trackRecordRejectedMock = trackRecordRejected as jest.Mock;
// Call some code internally including "./metrics" and calling `trackRecordPushed`
// Verify:
expect(trackRecordPushed).toHaveBeenCalledWith(3); // check arguments also

jest-dynamodb

Jest has excellent plugin to setup test DynamoDB within tests

https://jestjs.io/docs/en/dynamodb

yarn add @shelf/jest-dynamodb --dev

Use as preset (package.json/jest)

{
  "preset": "@shelf/jest-dynamodb"
}

Or provision manually (package.json/jest)

{
  "globalSetup": "@shelf/jest-dynamodb/setup.js",
  "globalTeardown": "@shelf/jest-dynamodb/teardown.js",
  "testEnvironment": "@shelf/jest-dynamodb/environment.js"
}

Describe databases in jest-dynamodb-config.js:

module.exports = {
  tables: [
    {
      TableName: `files`,
      KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
      AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }],
      ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 },
    },
  ],
};

In tests create client connected to test database:

import { DynamoDB } from "aws-sdk";
const config = {
  convertEmptyValues: true,
  endpoint: "localhost:8000",
  sslEnabled: false,
  region: "local-env",
};

const docClient = new DynamoDB.DocumentClient(config);