かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Vite + TypeScript のプロジェクトに Jest x GitHub actions PR 時に coverage レポートを出力させたい

Zenn にメモしたのをまとめたものです

環境
  • vite 4.4.9
  • typescript 5.1.6
  • jest 29.6.4
  • ts-jest 29.1.1
  • @types/jest 29.5.4

Jest の導入

$ npm i -D jest ts-jest @types/jest
# Jestの設定ファイル作成
$ npx ts-jest config:init
# jest.config.js が生成される

jest.config.js

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx', 'node'],
  // test ファイルを置くディレクトリ
  roots: ['<rootDir>/src/'],
  // カバレッジを出力するディレクトリ
  coverageDirectory: 'coverage',
};

ダミーのテストファイルを作成しテストが実行できれば OK

./src/check.test.ts

test("check", () => { console.log("OK") });

run test

$ npx run jest

npx run jest でエラーになるケース

package.json"type": "module" が指定されていると下記のようなエラーが発生する

$ npx run jest

ReferenceError: module is not defined in ES module scope<br /> This file is being treated as an ES module because it has a '.js' file extension and '/Users/<User>/path/to/project/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.

jest.config.js を module 形式に変換すればOK

jest.config.js

/** @type {import('ts-jest').JestConfigWithTsJest} */
- module.exports = {
+ export default {
    preset: 'ts-jest',
    testEnvironment: 'node',
    moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx', 'node'],
    // test ファイルを置くディレクトリ
    roots: ['<rootDir>/src/'],
    // カバレッジを出力するディレクトリ
    coverageDirectory: 'coverage',
  };

Jest を実行する npm script を作成

package.json

"scripts": {
  "test": "jest",
  "test:coverage": "jest --coverage",
}

GitHub Actions でテストを実行する

./github/workflows/pr-test.yml

name: pr-test
on:
  pull_request:
    branches: [$default-branch]
    paths:
      - '.github/workflows/pr-test.yml'
      - 'src/**'
      - 'package.json'
      - 'tsconfig.json'
jobs:
  test:
    name: lint & build
    runs-on: ubuntu-latest
    timeout-minutes: 5
    strategy:
      matrix:
        node-version: ['18', 'lts/*']
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: setup
        run: npm ci
      - name: Run ESLint
        run: npm run lint
      - name: Run test
        run: npm run test:coverage
      - name: build
        run: npm run build

PR 作成時に actions が実行されれば OK

coverage を PR のコメントに残す

Jest Coverage Comment を使って coverage をコメントとして出力させる

jest-junit をインストール

$ npm i -D jest-junit

jest-junit.xml を coverage ディレクトリに出力する

jest.config.js

export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx', 'node'],
  // test ファイルを置くディレクトリ
  roots: ['<rootDir>/src/'],
  // カバレッジを出力するディレクトリ
  coverageDirectory: 'coverage',
+ reporters: ['default', ['jest-junit', { outputDirectory: 'coverage' }]], 
};

json-summary と covarage.txt を生成する

デフォルトでは Jest Coverage Comment で使用する coverage-summary.json が生成されないので生成するオプションを設定する
coverageReporters['text', { file: 'coverage.txt' }]'json-summary' を追加すればOK

jest.config.js

export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx', 'node'],
  // test ファイルを置くディレクトリ
  roots: ['<rootDir>/src/'],
  // カバレッジを出力するディレクトリ
  coverageDirectory: 'coverage',
  reporters: ['default', ['jest-junit', { outputDirectory: 'coverage' }]], 
+ coverageReporters: ['clover', 'json', 'lcov', ['text', { file: 'coverage.txt' }], 'json-summary']
};

coverage の確認

npx run jest --coverage で下記のように出力されていればOK

/coverage
  |- /lcov-report
  |- clover.xml
  |- coverage-final.json
  |- coverage-summary.json
  |- coverage.txt
  |- junit.xml
  |- lcov.info

Coverage を PR にコメントする actions を追加する

./github/workflows/pr-test.yml

    steps:
      - uses: actions/checkout@v3
      # 略
      - name: Run test
        run: npm run test:coverage
+     - name: Jest Coverage Comment
+       uses: MishaKav/jest-coverage-comment@v1.0.23
+       with:
+         summary-title: Coverage Summary
+         coverage-summary-path: ./coverage/coverage-summary.json
+         junitxml-title: JUnit
+         junitxml-path: ./coverage/junit.xml
+         coverage-title: Coverage Details
+         coverage-path: ./coverage/coverage.txt
      - name: build
        run: npm run build

PR に新しいコミットを作成して workflow が実行され coverage がコメントされるようになっていればOK

最終的な jest.config.js, GitHub actions

jest.config.js

export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx', 'node'],
  // test ファイルを置くディレクトリ
  roots: ['<rootDir>/src/'],
  // カバレッジを出力するディレクトリ
  coverageDirectory: 'coverage',
  reporters: ['default', ['jest-junit', { outputDirectory: 'coverage' }]],
  coverageReporters: ['clover', 'json', 'lcov', ['text', { file: 'coverage.txt' }], 'json-summary']
};

./github/workflows/pr-test.yml

name: pr-test
on:
  pull_request:
    branches: [$default-branch]
    paths:
      - '.github/workflows/pr-test.yml'
      - 'src/**'
      - 'package.json'
      - 'tsconfig.json'
jobs:
  test:
    name: lint & build
    runs-on: ubuntu-latest
    timeout-minutes: 5
    strategy:
      matrix:
        node-version: ['18', 'lts/*']
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: setup
        run: npm ci
      - name: Run ESLint
        run: npm run lint
      - name: Run test
        run: npm run test:coverage
      - name: Jest Coverage Comment
        uses: MishaKav/jest-coverage-comment@v1.0.23
        with:
          summary-title: Coverage Summary
          coverage-summary-path: ./coverage/coverage-summary.json
          junitxml-title: JUnit
          junitxml-path: ./coverage/junit.xml
          coverage-title: Coverage Details
          coverage-path: ./coverage/coverage.txt
      - name: build
        run: npm run build

🥔 Tips ts-jest[config] (WARN) message TS151001

Jest 実行時に下記の warning が出る場合

ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting esModuleInterop to true in your TypeScript configuration file (usually tsconfig.json). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.

tsconfig"esModuleInterop": true, を設定すると warning が消える

{
  "compilerOptions": {
    "esModuleInterop": true,
  }
}

cf. TypeScriptのesModuleinteropフラグを設定してCommonJSモジュールを実行可能とする | DevelopersIO

おわり


[参考]