Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.
/ joke Public archive

Typesafe mock utility with minimal boilerplate for jest

License

Notifications You must be signed in to change notification settings

userlike/joke

Repository files navigation

🍭 joke

joke is a typesafe, boilerplate free version of jest.mock.

Advantages

  • Less boilerplate than jest.mock.
  • Type-safe imports. No more type-casting.
  • TS/JS Language Server recognizes them when moving files around.
  • Supports partial mocking (mockSome).

Usage

Install

npm install --saveDev @userlike/joke @userlike/babel-plugin-joke
yarn add -D @userlike/joke @userlike/babel-plugin-joke

Babel config

Add @userlike/babel-plugin-joke to your babel plugins.

If you use ts-jest, please additionally refer to the "Usage with ts-jest" section below.

And use

import { mock } from "@userlike/joke";

const { fetchUser } = mock(import("./service"));

fetchUser.mockReturnValue(Promise.resolve({ id: 1, name: "Jane Doe" }));

Full mocking

Auto-mock the whole module.

import { mock } from "@userlike/joke";

const { fetchUser } = mock(import("./service"));

fetchUser.mockReturnValue(Promise.resolve({ id: 1, name: "Jane Doe" }));

Full mocking with partial implementation

Use the second argument of mock to provide a partial implementation. Behind the scenes, it extends auto-mocked module with the given implementation using Object.assign.

import { mock } from "@userlike/joke";

const { fetchUser } = mock(import("./service"), () => ({
  fetchUser: () => Promise.resolve({ id: 1, name: "Jane Doe" })
}));

Partial mocking

When you need to mock a module partially and want to keep the rest of the module unmocked, you can use mockSome. Behind the scenes, it uses jest.requireActual by extending its actual implementation with the given implementation using Object.assign.

import { mockSome } from "@userlike/joke";
import { renderUser } from "./my-component";

// fetchUser is mocked, getUserId is the real implementation
const { fetchUser, getUserId } = mockSome(import("./service"), () => ({
  fetchUser: jest.fn()
}));

test(async () => {
  const user = { id: 1, name: "Jane Doe" };
  fetchUser.mockReturnValue(Promise.resolve(user));

  await renderUser();

  expect(document.getElementById("#user-id").innerText).toBe(getUserId(user));
});

Full replacement

When you want to skip auto-mocking, you can use mockAll. It's equivalent to jest.mock(module, moduleFactory).

import { mockAll } from "@userlike/joke";
import { renderUser } from "./my-component";

const { fetchUser } = mockAll(import("./service"), () => ({
  fetchUser: jest.fn()
}));

test(async () => {
  const user = { id: 1, name: "Jane Doe" };
  fetchUser.mockReturnValue(Promise.resolve(user));

  await renderUser();

  expect(document.getElementById("#user-id").innerText).toBe(getUserId(user));
});

Usage with ts-jest

If you use ts-jest instead of Babel, you need to additionally ensure each of the following:

  • That Babel preprocessing is enabled in your ts-jest configuration section.
  • That Babel is configured to use joke as a plugin.
  • That the module key of tsconfig.json's compilerOptions is set to at least es2020, or esnext to support dynamic imports. You may also need to set moduleResolution to node for the general import syntax to work properly.
  • That the code is transpiled down to the JS syntax jest understands (you may use @babel/preset-env for that purpose).

Note: if you don't want to modify your main tsconfig.json file, you can introduce a separate configuration named e.g. tsconfig.tests.json.

Example Typescript configuration for tests:

{
  "extends": "tsconfig.json",
  "compilerOptions": {
    "module": "ES2020",
    "moduleResolution": "node"
  }
}

To enable Babel preprocessing in ts-jest, as well as to configure the tsconfig file you want use for tests, add or update the globals section in your jest config.

Example with separate Babel and Typescript configuration files:

"globals": {
  'ts-jest': {
    "babelConfig": "true",
    "tsConfig": "tsconfig.test.json"
  }
}

Example with inline Typescript and Babel configuration:

"globals": {
  'ts-jest': {
    "babelConfig": {
      "plugins": ["@userlike/babel-plugin-joke"],
      "presets": [
        [
          "@babel/preset-env",
          {
            "targets": {
              "node": 'current'
            }
          }
        ]
      ]
    },
    "tsConfig": {
      "module": "es2020",
      "moduleResolution": "node",
    }
  }
}

For details, see ts-jest configuration docs.