typescriptjestjsts-jestobsidian

Jest configuration / error - Cannot find module "<module-name>" from "<test-filename>"


Jest Error when launching tests

I'm building a plugin for Obsidian and I setup my project from their plugin template on GitHub.

To produce a robust plugin, I want to setup unit testing with Jest. Then, I made a simple unit test for my plugin and imported the obsidian module in the test file. However, when launching the test, it produces the following error :

 FAIL  tests/sync_algorithm.test.ts
  ● Test suite failed to run

    Cannot find module 'obsidian' from 'tests/sync_algorithm.test.ts'

      1 | import { syncAlgorithm } from "../src/sync_algorithm";
    > 2 | import { TFile } from "obsidian";
        | ^
      3 |
      4 | describe("Sync algorithm", () => {
      5 |       beforeEach(() => {});

      at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
      at Object.<anonymous> (tests/sync_algorithm.test.ts:2:1)

My configuration

IDE : VS Code

package.json

Here are the important informations from the package.json file :

{
  ...
  "scripts": {
    ...
    "test": "jest"
  },
  ...
  "devDependencies": {
    "@codemirror/state": "^6.2.0",
    "@codemirror/view": "^6.8.1",
    "@types/jest": "^29.4.0",
    "@types/node": "^16.18.12",
    "@typescript-eslint/eslint-plugin": "5.29.0",
    "@typescript-eslint/parser": "5.29.0",
    "builtin-modules": "3.3.0",
    "esbuild": "0.17.3",
    "eslint": "^8.34.0",
    "jest": "^29.4.2",
    "obsidian": "^1.1.1",
    "ts-jest": "^29.0.5",
    "ts-node": "^10.9.1",
    "tslib": "2.4.0",
    "typescript": "4.7.4"
  }
}

jest.config.ts

export default {
    moduleDirectories: ["node_modules", "node_modules/.pnpm", "src"],
    modulePaths: ["<rootDir>"],
    moduleFileExtensions: [
        "js",
        "mjs",
        "cjs",
        "jsx",
        "ts",
        "tsx",
        "json",
        "node",
    ],
    moduleNameMapper: {
        // obsidian:
        //  "<rootDir>//node_modules/.pnpm/obsidian@1.1.1_cknrwgf45skglbt6g7kref4zeq/node_modules/obsidian",
        "./node_modules/(.*)": "<rootDir>/node_modules/$1",
    },
    transform: {
        "^.+\\.ts$": "ts-jest",
    },
};

tsconfig.json

{
    "compilerOptions": {
        "baseUrl": ".",
        "inlineSourceMap": true,
        "inlineSources": true,
        "module": "ESNext",
        "target": "ES6",
        "allowJs": true,
        "noImplicitAny": true,
        "moduleResolution": "node",
        "importHelpers": true,
        "isolatedModules": true,
        "strictNullChecks": true,
        "esModuleInterop": true,
        "lib": [
            "DOM",
            "ES5",
            "ES6",
            "ES7"
        ]
    },
    "include": [
        "**/*.ts"
    ],
}

What I've tried

Changing package manager

I first installed the project using pnpm. However, as this package manager uses symlinks for managing packages, I thought it would be the cause of the problem.

So I changed and reinstalled all dependencies using npm. However, the bug still appeared.

I also tried yarn but the error still there.

Changing config files (the ones above)

I tried lots of changes in the packages I use, the jest.config.ts or jest.config.js and the tsconfig.json files.

Other tries

I tried changing package manager and configuration according to searches I made.

Here are some already built plugins for Obsidian that use Jest for testing. I tried to reproduce their configurations :

The latter is the one I've found a jest config file and tests files that imports obsidian module. I cloned it locally and ran successfully all its tests.

But it didn't work when reproducing configs in my repo.

My searches on this issue

Here are some ressources I read to change the configuration accordingly and resolve this error :

Useful ressources for setting up Jest and TypeScript

Thank you in advance for any help or suggestion to make it working.


Solution

  • Why didn't you mock the module? Like:

    jest.mock('./moduleName') //replace with obsidian
    

    or parts (function / construct etc.) you want to test:

    jest.mock('./moduleName', () => ({
      <function>: () => jest.fn(),
    }));
    

    see Jest.io Manual Mocks

    Example for obsidian/TFile:

    // Create a mock for the TFile class
    jest.mock('obsidian', () => {
      return {
        TFile: jest.fn().mockImplementation(() => {
          return {
            // Add any methods or properties you want to mock
            basename: 'mock-basename.md',
            path: 'mock-path',
            extension: 'md',
            stat: jest.fn(),
          };
        }),
      };
    });
    

    I would suggest this approach, because you don't need to test the library. You should aim to test the functionality of your code in isolation from external dependencies. This is because external dependencies such as libraries can be unpredictable, and their behavior can change without your knowledge, making your tests unreliable.