💯
MapLibre GL JSをvitestでモックする
背景
コンポーネントがMapLibre GL JSを含む場合、以下のようなエラーが発生します
FAIL src/routes/map/page.test.ts [ src/routes/map/page.test.ts ]
TypeError: window.URL.createObjectURL is not a function
❯ define node_modules/maplibre-gl/dist/maplibre-gl.js:34:44
32|
33| if (typeof window !== 'undefined') {
34| maplibregl.setWorkerUrl(window.URL.createOb…
| ^
35| }
36|
エラーを見るに、MapLibre GL JSがブラウザでのみ利用できるAPIに依存していることが原因でしょう。
対処法
MapLibre GL JS自体をモックしてしまうのが良さそう。MapLibre GL JS自体の動作をテストする必要はないので(ライブラリ側でユニットテストされているし、実際の動きをテストしたい場合はE2Eテストでカバーすることになるだろう)。
サンプルコード
@testing-library/svelte
を用いたコンポーネントのユニットテストを例としますが、ReactやVueでも同様の手法をとれます。
import { render } from '@testing-library/svelte';
import { describe, test, vi } from 'vitest';
import MapPage from './+page.svelte';
vi.mock('maplibre-gl', () => ({
default: vi.fn(),
Map: vi.fn().mockImplementation(() => ({
setStyle: vi.fn(),
once: vi.fn(),
on: vi.fn(),
addControl: vi.fn()
})),
GeolocateControl: vi.fn(),
NavigationControl: vi.fn()
// ほかにも大量の実装があるが、コンポーネントで呼び出されるAPIだけモックすればよい
}));
describe('Map', () => {
test('render', async () => {
render(MapPage);
});
});
モックした関数がコールされることをテスト
モジュールをモックしたうえで、そのモック関数がコールされたかどうかチェックしたい場合、シンプルなケースでは以下のように書くだけで済みます。
import { Map } from 'maplibre-gl'; // importされるのはモック関数
// モック処理は省略
describe('MapPage', () => {
test('render', async () => {
render(MapPage);
expect(Map).toHaveBeenCalledTimes(1);
});
});
以下はSveltekitに限った内容かもしれません
ただし、MapLibre GL JSはDOMを操作する関数なので、副作用ーSvelteではonMount
を利用してMapクラスを初期化することになります。しかし、特殊な設定をしないとonMount
が発火せず、上記のテストコードは失敗します(Mapが呼ばれていないことになるため)。
onMount
をテスト環境で動かすためには、少しテクいことをしなければならなそうです。
以下のように、configを修正することで、render()
でonMount
処理が走り(=Mapクラスが初期化され)、テストが成功します(どういう意味を持つ設定なのかはよくわからない)。
vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig, type Plugin } from 'vite';
// onMount処理のテストのためのプラグイン
const vitestBrowserConditionPlugin: Plugin = {
name: 'vite-plugin-vitest-browser-condition',
config({ resolve }) {
if (process.env.VITEST) {
resolve?.conditions?.unshift('browser');
}
}
};
export default defineConfig({
plugins: [vitestBrowserConditionPlugin, sveltekit()],
// @ts-ignore
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
globals: true,
environment: 'jsdom'
}
});
Discussion