Svelteのユニットテスト事情 (2021年12月版)
はじめに
この記事は Svelte Advent Calendar 2021 の 17 日目の記事です。
筆者は仕事の関係もあり、最近本格的に React/Vue でユニットテストを書くようになりました(えっ今更すぎるだって?色々訳がありまして…)。その流れで、そういえば Svelte のユニットテスト事情どうなっているのだろうと考えるようになり、書き方の勉強も兼ねて本記事を書くことにしました。
Svelte のユニットテストを書く方法
公式 Q&A の「How do I test Svelte apps?」には次の方法が紹介されています。
- Svelte Testing Libraryを使う方法
- uvuを使う方法
ただし、後述する Svelte Society の公式レシピなど前者の方法で紹介されることが殆どで、Testing Library を使えば問題ないです(実際、Svelte のコアメンバーが Svelte Testing Library の公式リポジトリにコントリビュートしてたりする)。
Svelte Testing Library について
React のテストを書いてる人にはお馴染み(だと思いたい)Testing Libraryの Svelte 版になります。正直なところ、その認識だけで問題ありません。
書き方
基本的な書き方は次のサイトを見れば問題ないです。Context API やコンポーネントで持っている情報の取得/叩き方がちょっと特殊なくらいです。
Svelte 自体、React 以上に外部ライブラリの選択肢が幅広いため、ちょっと特殊な書き方をしたくなったときはReact Testing Libraryも参考にしながら書いていけば、あまり迷うことなく書けるかと。
開発環境
PC: MacBook Pro(Apple M1, 2020)
OS: macOS Monterey 12.0.1
環境構築
ここが一番の鬼門です(特に TypeScript で書くとなる場合)。残念ながら、React での create-react-app
、Vue の Vue CLI
のような形で、Svelte では公式から楽に設定してくれる方法は現状提供されていません…。一応もう少し楽に設定する方法はあるため、今回は Svelte Testing Library 公式で書かれている手順と、有志作成のリポジトリを使って楽めに設定する方法の 2 つを紹介します。
公式に沿った手順
基本的には、Svelte Testing Library の Setup ページと svelte-jester の README を参考にしました。
ただしこれだと複数ライブラリのドキュメントを横断しなければいけず、わかりにくかったため、こちらの記事も参考にしました。
(babel
や jest
の設定、 svelte-preprocess
周り本当に弱いので勉強しなければ…)
1. Svelte + TypeScript のテンプレートを生成
今回は svelte-typescript-test
というプロジェクト名で作成するので、npx degit
コマンドでこちらの名称を指定し、作成します。
npx degit sveltejs/template svelte-typescript-test
cd svelte-typescript-test
node scripts/setupTypeScript.js
yarn install # OR npm install
2. ユニットテスト書くのに必要なライブラリのインストール
インストールする必要があるライブラリは以下となります。「えっ多くない…」ってなりそうですが、マニュアルインストールする以上、仕方ないかと。
ライブラリ名の頭について
- TypeScript を使う場合のみ必要になるライブラリには、 ☆ がついています
- TypeScript のみでテストする場合、インストールする必要がないライブラリには、 △ がついています
- jest : ご存知フロントのユニットテストツール
- ☆ ts-jest : TypeScriptでJestを使えるようにしてくれるライブラリ
- @testing-library/svelte : Svelte Testing Library本体
- @testing-library/jest-dom : toBeInTheDocumentなどDOM要素関係のTesting Library
- svelte-jester : .svelteファイルでJestを使えるようにしてくれるライブラリ
- ☆ @types/jest : Jestの型定義
- △ babel-jest : JavaScriptでJestを使えるようにしてくれるライブラリ
- △ @babel/preset-env : babel-jestとセットで使うライブラリ
それではインストールしていきます。今回は vanilla js でテストコードを書く予定はないので、babel-jest
/@babel/preset-env
についてはインストールしません。
yarn add -D \
jest ts-jest \
@testing-library/svelte @testing-library/jest-dom \
svelte-jester @types/jest
# OR
# npm install --save-dev ...
3. 設定ファイルの作成
まず package.json
に Jest 用の script を追加しておきます。
--watch
をつけておくと、Jest がファイルの変更を検知してくれるので便利です。
{
"scripts": {
...
+ "test": "jest",
+ "test:watch": "npm test --watch"
}
}
package.json
にさらに追加します。
babel-jest
はインストールしていないので、transform
の中に指定する必要はありません。一方、moduleFileExtensions
には必ず js
を指定しないといけないため、入れておきます。さらに @testing-library/jest-dom
を使う関係で、testEnvironment
も指定しておきます。
※ここは package.json
ではなく、プロジェクト直下に jest.config.js
を作成して、"transform": {
以降の内容を追記しても大丈夫かと。
{
"scripts": {
...
+ "jest": {
+ "transform": {
+ "^.+\\.svelte$": [
+ "svelte-jester",
+ {
+ "preprocess": true
+ }
+ ],
+ "^.+\\.ts$": "ts-jest"
+ },
+ "moduleFileExtensions": [
+ "js",
+ "ts",
+ "svelte"
+ ],
+ "testEnvironment": "jsdom"
+ }
}
}
package.json
への追記はここまでです。
次は svelte.config.js
をプロジェクト直下に新規で作成します。svelte-preprocess
は Svelte アプリの作成した際に、一緒にインストールされるため、気にする必要はありません。
TypeScript だけでテスト実行する場合、作成しなくても通ったという情報もありましたが、こちらの環境だと、ないとテストが実行できませんでした…。
+ const sveltePreprocess = require("svelte-preprocess");
+
+ module.exports = {
+ preprocess: sveltePreprocess(),
+ };
最後に tsconfig.json
へ 3 行追記します。
インストールした @types/jest
をここに指定することで Jest の型定義がグローバルに有効化されます。
{
...
+ "compilerOptions": {
+ "types": ["jest"]
+ }
}
設定は以上です。
4. Svelte ファイルのテストを書く
ようやく本題です。
記事で作成するプロジェクトコードは以下のページに設置しています。
今回の記事では文字数の都合上、Svelte コンポーネントの基礎的な書き方のみを取り上げ、Jest の書き方・メソッドに関しての説明は省略します。
もう少し本格的なものがみたい方は、 Todo アプリのテストコードも置いたので、併せて見てみてください。
まずは「1. Svelte + TypeScript のテンプレートを生成」で生成された App.svelte
にある name
という props
に適当な文字列を渡したらテンプレートに表示されるという、一番シンプルな例を挙げてみます。
<script lang="ts">
export let name: string;
</script>
<main>
<h1>Hello {name}!</h1>
<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>
import "@testing-library/jest-dom";
import { render } from "@testing-library/svelte";
import App from "../App.svelte";
// h1タグの文字列が「Hello test!!!!」と一致していることを確認
test("should render App component", () => {
const { getByRole } = render(App, {
name: "test!!!",
});
expect(getByRole("heading").textContent).toBe("Hello test!!!!");
});
もう 1 つ今回挙げる例として、ボタンをクリックすると合計値が変化する例も紹介します。
sum の中の数値がリアクティブに変化することを確認するテストになります。tsconfig.json
<script lang="ts">
let numbers = [0, 1, 2, 3]
const addNumber = () => {
numbers = [...numbers, numbers.length]
}
$: sum = numbers.reduce((previous, current) => previous + current, 0)
</script>
<button on:click={addNumber}>
Add number
</button>
<p>sum: {sum}</p>
import { fireEvent } from "@testing-library/dom";
import "@testing-library/jest-dom";
import { render } from "@testing-library/svelte";
import Button from "../components/Button.svelte";
test("should fire onclick event", async () => {
const { getByText } = render(Button);
const button = getByText("Add number");
const printSum = getByText("sum: 6");
// ボタンをクリック(1回目)
await fireEvent.click(button);
expect(printSum.textContent).toBe("sum: 10");
// ボタンをクリック(2回目)
await fireEvent.click(button);
expect(printSum.textContent).toBe("sum: 15");
});
有志作成のライブラリ/テンプレートを使って、設定する方法
マニュアルインストールしなくても Svelte Testing Library を使う方法は一応存在します。
テンプレートを使う方法
Svelte 公式ではテンプレート一覧が記載されており、以下のサイトから探すことが出来ます。
ただし最終更新が 2019 年だったりで、これから新規で Svelte プロジェクトを作成する際に使用するのはちょっと厳しい気がします。
また、環境構築の節で参考にした記事として名前が出ていた Dave Ceddia 氏もテンプレートを載せています(更新が1年前から止まっているのが残念ですが…)。
そろそろアクティブに更新されるテンプレートが出てほしいところです自分で作れって話ですが。
ライブラリを使う方法
現状一番安定しているのは「Svelte Add
を使う方法」な気がします。
前述のテンプレート一覧ページと同じページに記載されているなど、実質公式公認のプロジェクトとなっています。
掲載されているライブラリを指定することで、簡単に SvelteKit アプリへ追加できます。
有志作成のものもあり、以下のライブラリを使用することによって Jest を簡単に追加することが出来ます。TS 化も可能です。
本記事の最後は svelte-add-jest で生成する手順の記載をもって、締めます。
svelte-add-jest の適用手順
本節で作成したアプリのリポジトリページはこちらです。
まずは SvelteKit アプリを新規作成します。
色々質問されますが、すべて Yes を選択して OK です(TypeScript の有効化も忘れずに!)。
npm init svelte@next 作成するアプリ名
svelte-add-jest を適用するため、README にかかれている通り、
tsconfig.json
のコメントアウトされている箇所をすべて削除しておく必要があります。
しないと、svelte-add-jest の適用に失敗します。
それでは svelte-add-jest を適用していきます。TypeScript 用設定はデフォルトで無効になっているため、--ts
フラグを付ける必要があります。
また、こちらの環境ではつけないとインストールに失敗したため、--no-ssh
フラグも併せてつけておきます。
npx apply rossyman/svelte-add-jest --ts --no-ssh
これで npm run test
(yarn test
)を叩くと一旦ユニットテストが通ります。
ただしここで1点問題が発生します。
次のテストコードが生成されるのですが、describe
/ test
、expect
の型定義が不明になっており、ts の型エラーが発生します。npm i --save-dev @types/jest
を叩いてねと出てくるのですが、package.json
には存在しており、インストール済みの状態でした。
describe('Index', () => {
let renderedComponent: RenderResult;
beforeEach(() => {
renderedComponent = render(Index);
});
describe('once the component has been rendered', () => {
test('should show the proper heading', () => {
expect(renderedComponent.getByText(/SvelteKit/)).toBeInTheDocument();
});
});
});
型定義が不明なままでも、テストは通るのですが、上記で挙げている Jest の関数がコード補完で呼び出せないため、かなり書きづらくなってしまいます。
原因を調べたところ、次の2点が原因でした。
-
tsconfig.json
を確認したところ、compilerOptions
のtypes
にjest
が定義されておらず、Jest の型定義がグローバルに有効化されていない。 -
src/**/*.spec.ts
に該当するテストファイルが exclude されていて、tsconfig
が読み込まれていなかった。
今回とった解決策は以下の通りです。
まず、生成される tsconfig.spec.json
のコードを削除しました(ファイル削除すると、動かなくなる可能性があったため、今回は一旦コードの削除で留めました)。
- {
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "isolatedModules": false
- },
- "exclude": ["src/**"],
- "include": ["src/**/*.spec.ts"]
- }
コード削除出来たら、次は tsconfig.json
に Jest の型定義を読み込ませます。
{
...
+ "compilerOptions": {
+ "types": ["jest"]
+ }
// @types/jestをグローバルに適用させるため、excludeさせない
- "exclude": ["src/**/*.spec.ts"]
}
これでもう一度 yarn test
or npm run test
するとテストが通るはずです。
Jest の型定義が読み取れない問題に関しては、こちらの質問が詳しいです。
本記事は以上となります。状況が変わり次第、またどこかにまとめる予定です。
補足として Svelte のユニットテストを書く上で、参考になるページを一覧でまとめておきます。参考になれば幸いです(文中で登場した記事リンクも一部ここで再掲しています)。
Discussion