🙌

Vitest テストコードを実装ファイルと同一のファイルに記述する

2022/03/08に公開約3,200字

例えば Rust などの言語はテストコードを実装ファイルと同一のファイルに書くことができます。


#![allow(unused)]
fn main() {
pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

https://doc.rust-jp.rs/book-ja/ch11-03-test-organization.html#非公開関数をテストする

実装と同一のファイルにテストコードを記述するメリットとして以下のような点があります。

  • private にしたい目的で export したくない関数をテストできる
  • 実装とテストの距離が近いのでテストが書きやすい(私はテストコードを書くときだけいつもエディタの画面を分割して表示してます)
  • さっとプロトタイプのコードを書くたいときに素早く書ける

JavaScript には Rust のように言語仕様として同一ファイルにテストを書く機能が備わってはいないのですが、これを実現するための工夫が書かれた記事が見受けられます。

https://zenn.dev/mizchi/articles/inline-testing

https://zenn.dev/ptpadan/articles/jest-same-test-file

そしてどうやら Vitest と呼ばれるテストツールは同一ファイルにテストコードを書く方法を提供しているようですので、これを試してみます。

Vitest の環境構築

Vitest を利用するには Vite の v2.7.10 以降と Node の v14 移行が必要です。適当に Vite のプロジェクトを作成します。

$ npm create vite -- --template vanilla-ts

Vitest をインストールします。v0.6.0 以上が必要です。

$ npm i vitest

諸々の設定を記述します。まずは vite.config.ts ファイルを作成します。

vite.config.ts
// vite.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  define: {
    'import.meta.vitest': false,
  },
  test: {
    includeSource: ['src/**/*.{js,ts}'],
  },
})

define オプションはプロダクションビルド時にテストコードがバンドルファイルに含まれないようにするための設定です。

test.includeSourcesrc 配下のファイルがテスト対象であることを伝えます。

続いて tsconfig.jsonimport.meta.vitest の型情報を追加します。

tsconfig.json
{
  "compilerOptions": {
+    "types": [
+      "vitest/importMeta"
+    ]
  }
}

テストコードを書く

それでは、テストコードを書いてみましょう。

const add = (a: number, b: number) => {
  return a + b
}

if (import.meta.vitest) {
  const { describe, it, expect } = import.meta.vitest
  describe('calc', () => {
    it('add', () => {
      expect(add(1, 2)).toBe(3)
    })
  })
}

テストコードは if (import.meta.vitest) のブロック内に記述します。テストコードがランタイムで取り除かれるようにするためですね。

通常 Vitest を使用するときは describeexpect などは import する必要がありますが、その方法ですとやはりランタイム含まれてしまうため import.meta.vitest オブジェクトより取得します。

それではテストを実行してみましょう。

$ npx vitest

 DEV  v0.6.0 ~/vite-project

 √ src/calc.ts (1)

Test Files  1 passed (1)
     Tests  1 passed (1)
      Time  5.30s (in thread 3ms, 179055.32%)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

問題なくテストが実施されていますね。

その他、例えば lodash.cloneDeep のようにテスト時のみに使用したいライブラリがあることでしょう。そのような場合にトップレベルで import 文を記述するとそのライブラリがバンドルファイルに含まれてしまします。

テスト時のみに使用したいライブラリがある場合には if (import.meta.vitest) ブロク内で dynamic import を使います。

const add = (a: number, b: number) => {
  return a + b
}

const obj = {
  a: 1,
  b: {
    c: 2,
  }
}

if (import.meta.vitest) {
  const { describe, it, expect } = import.meta.vitest
  const { cloneDeep } = await import('lodash-es')

  const copy = cloneDeep(obj)

  describe('calc', () => {
    it('add', () => {
      expect(add(copy.a, copy.b.c)).toBe(3)
    })
  })
}

参考

https://vitest.dev/guide/features.html#in-source-testing

https://twitter.com/antfu7/status/1500377124267429890
GitHubで編集を提案

Discussion

ログインするとコメントできます