■ はじめに
https://dk521123.hatenablog.com/entry/2021/01/07/230317
https://dk521123.hatenablog.com/entry/2021/01/27/225148
https://dk521123.hatenablog.com/entry/2021/03/13/000000
https://dk521123.hatenablog.com/entry/2021/03/23/231011
の続き。 今回は、前回行った axios の モック化をもう少し掘り下げる。 結構、Github の README が充実していて、分かりやすい
https://github.com/ctimmerm/axios-mock-adapter
目次
【1】モックのURLが合致しなかった場合の対処 - onNoMatch 1)onNoMatch: 'throwException' 2)その他の方法 【2】複数のAPIに対応するには 1)正規表現を使う 2)onAnyを使う 補足:複数のMockを纏めて定義する 【3】ネットワークエラー/タイムアウトの試験を行うには 【4】後処理
【1】モックのURLが合致しなかった場合の対処 - onNoMatch
mockAxios.onGet('https://xxxx/users').reply(... と指定した場合、「https://xxxx/users」以外がきた場合の対処。 => MockAdapterインスタンス作成時に指定できる
1)onNoMatch: 'throwException'
// 一致しなかった場合、例外 mockAxios = new MockAdapter(axios, { onNoMatch: 'throwException' });
補足 - onNoMatch: 'passthrough'
// 一致しなかった場合、無視 // Mock all requests to /foo with HTTP 200, but forward // any others requests to server mockAxios = new MockAdapter(axios, { onNoMatch: 'passthrough' });
2)その他の方法
「【2】複数のAPIに対応するには」の「B) onAnyを使う」で記載。 => サンプルの場合、「else」で500番を返すようにしている
【2】複数のAPIに対応するには
1)正規表現を使う
その1
mock.onGet(/\/users\/\d+/).reply(function (config) {
その2
const usersUri = "/users"; const url = new RegExp(`${usersUri}/*`); mock.onGet(url).reply(200, users);
2)onAnyを使う
// Expected order of requests: const responses = [ ["GET", "/foo", 200, { foo: "bar" }], ["POST", "/bar", 200], ["PUT", "/baz", 200], ]; // Match ALL requests mock.onAny().reply((config) => { const [method, url, ...response] = responses.shift(); if (config.url === url && config.method.toUpperCase() === method) return response; // Unexpected request, error out return [500, {}]; });
補足:複数のMockを纏めて定義する
const initializeMocks = (additionalMocks = {}) => { const mocks = { mock1: () => { mockAdapter.onGet('xxxx').reply((config) => { return [200, { id: 'x-001'}]; }); }, mock2: () => { mockAdapter.onGet('yyyy').reply((config) => { return [200, { id: 'x-002'}]; }); }, ...additionalMocks, }; Object.values(mocks).forEach(mock => { mock(); }); return mocks; }
サンプル
* 以下の関連記事のサンプルを上記の書き方で書き直してみる
https://dk521123.hatenablog.com/entry/2021/03/23/231011
import { shallowMount, Wrapper } from '@vue/test-utils'; import Demo2 from '@/views/Demo2.vue'; import flushPromises from 'flush-promises'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; describe('Demo2.vue', () => { let wrapper: Wrapper<Demo2>; let mockAxios: MockAdapter; console.info = jest.fn(); console.error = jest.fn(); const initializeMocks = (overrideMocks = {}) => { const mocks = { todosMock: () => { const regExp = new RegExp('.*/todos/(.+)'); mockAxios.onGet(regExp).reply((config) => { let mockData = {}; const result = config.url?.match(regExp); if (result) { mockData = { userId: 1, id: result[1], title: 'sample', completed: false, }; } return [200, mockData]; }); }, usersMock: () => { mockAxios.onGet(new RegExp('.*/user')).reply(() => [200, [ { id: 1, name: 'Mike', email: 'mike@gmail.com', }, { id: 2, name: 'Tom', email: 'tom@gmail.com', }, { id: 3, name: 'Sam', email: 'sam@gmail.com', }, ]]); }, ...overrideMocks, }; Object.values(mocks).forEach((mock) => { mock(); }); return mocks; }; beforeEach(() => { mockAxios = new MockAdapter(axios, { onNoMatch: 'throwException' }); }); it('This is just a demo test', async () => { initializeMocks(); wrapper = shallowMount(Demo2); await flushPromises(); const vm = (wrapper.vm as any); console.log(vm.items); expect(vm.items[0].id).toEqual(1); expect(vm.items[0].name).toEqual('Mike'); expect(vm.items[0].email).toEqual('mike@gmail.com'); expect(vm.items[1].id).toEqual(2); expect(vm.items[1].name).toEqual('Tom'); expect(vm.items[1].email).toEqual('tom@gmail.com'); expect(vm.items[2].id).toEqual(3); expect(vm.items[2].name).toEqual('Sam'); expect(vm.items[2].email).toEqual('sam@gmail.com'); expect(console.info).toHaveBeenCalledWith('Response OK'); }); it('This is a network error test', async () => { const usersErrorMock = { usersMock: () => { mockAxios.onGet(new RegExp('.*/user')).networkError(); } }; initializeMocks(usersErrorMock); wrapper = shallowMount(Demo2); await flushPromises(); expect(console.error).toHaveBeenCalledWith('Error...'); }); });
4)ネットワークエラー/タイムアウトの試験を行うには
https://github.com/ctimmerm/axios-mock-adapter
より抜粋
Mock a low level network error
// Returns a failed promise with Error('Network Error'); mock.onGet("/users").networkError(); // networkErrorOnce can be used to mock a network error only once mock.onGet("/users").networkErrorOnce();
Mock a network timeout
// Returns a failed promise with Error with code set to 'ECONNABORTED' mock.onGet("/users").timeout(); // timeoutOnce can be used to mock a timeout only once mock.onGet("/users").timeoutOnce();
5)後処理
https://github.com/ctimmerm/axios-mock-adapter
mockAdapter.reset(); もしくは mockAdapter.restore() でモックのリセットができる。
reset / restore との違い
reset is different from restore in that restore removes the mocking from the axios instance completely, => restore() は完全にaxios インスタンスからモックを削除する whereas reset only removes all mock handlers that were added with onGet, onPost, etc. but leaves the mocking in place. => 一方、resetは、onGet, onPostなどに追加したモックハンドラ の全てを削除するが、モックはそのままにする。
関連記事
Jestを使った Vue / TypeScript の単体試験 ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2021/01/07/230317
Jestを使った Vue / TypeScript の単体試験 ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2021/01/27/225148
Jestを使った Vue / TypeScript の単体試験 ~ 非同期編 ~
https://dk521123.hatenablog.com/entry/2021/03/13/000000
Jestを使った Vue / TypeScript の単体試験 ~ axios 編 ~
https://dk521123.hatenablog.com/entry/2021/03/23/231011
非同期処理 ~ async/await, Promise ~
https://dk521123.hatenablog.com/entry/2021/01/16/202822
axios ~ 外部API通信ライブラリ ~
https://dk521123.hatenablog.com/entry/2021/01/14/101645
JS単体試験ツール Jest ~ Mock編 ~
https://dk521123.hatenablog.com/entry/2021/01/26/215558