インストール
vue cliを使い、vueプロジェクトにTestツールをインストール
vue add unit-jest
・jestとというJS用テストツールと、Vue Test UtileというVue用のツールがインストールされる。
VS Code
VS Codeで拡張を入れる。
- Jest Snippets
- Jest Runner
- Jest
・Jsetを入れると保存すると自動でテストが走る。
・Jest Runnerを入れるとテストの上部にRunとDebugというボタンが出るので、テストを一つ単位で実行できる。
・console.logデバッグより、デバッガーで値を見たほうがテストの場合早い。
フォルダ配置
・同じテスト対象と同じフォルダに__tests__という名前を付けてテストを配置する。というのがデファクトになっているようだ。
components
__tests__
Todo.spec.js // テストコード
Todo.vue // テスト対象
import Todo from "../Todo.vue"; // テスト対象を読み込む
test書き方
descriveでテストのまとまりを作る。itまたはtestという関数でテストを実行する。
・describeはネストできる。
・エディタにスニペットを入れておいたらdescと打つと出る。
describe('関数名とか', () => {
it('条件と結果とか', () => {
...
})
it("この時(when)、この場合、こういう結果が返る",() => {
...
let acutal = ... // actual 実際上の
expect(actual).toBe(true) // expect かなりの確信と理由をもって予期する
})
})
検証
とにかくexpectで包んで、条件を書く。=== ならtoBe()、toEqual、=== trueならtoBeTruthy() などなどなど。好きな書き方ができるhttps://jestjs.io/docs/ja/expect
https://qiita.com/t-toyota/items/93cce73004b9f765cfcf
expect(a).toEqual(1);
expect(wrapper).not.toBeNull();
事前処理、事後処理
describe内でBeforeAllやBeforeEachなどの関数を使って、複数のテストの事前処理、事後処理をまとめることができる。
describe( ...., () => {
// beforeAllは、配下のテスト群の前に一回だけ
let wrapper;
beforeAll(() => {
wrapper = mount(AAAComponent, {localVue});
});
// beforeEachは、配下のテストが実行される前に毎度一回ずつ
// afterEachは配下のテスト実行された後に一回ずつ
afterEach(() => {
...mockClear();
});
it(...
関数のモック
jest.fn()でモック用の関数を生成できる。
このモック用関数は、返す値を設定できたり、呼ばれた回数や引数が何だったか検証することができる。参考: https://jestjs.io/docs/ja/mock-functions
let f = jest.fn(); // モック関数を作る
// 関数が呼ばれたかわかる。
expect(f.mock.calls.length).toBe(0);
// vueのテストではvue test utilのvueコンポーネント内にモックする機能を使って、埋め込むことが多い。
let wrapper = mount(AAA , { mocks: {f} })
importのモック
以下のようなコードがあるとする。
import { API } from "@aws-amplify/api";
...
let res = await API.graphql(...)
この場合jest.mock()でimportをモックすることができる。
jest.mock("@aws-amplify/api", () => {
return { API: { graphql: jest.fn() } };
});
// jest.mockはdescribeやitの前に行う
describe(..
// テスト内からjest.mock内の関数にアクセスしたいならテスト内でもimportを書く。
import { API } from "@aws-amplify/api";
jest.mock("@aws-amplify/api", () => {
return { API: { graphql: jest.fn() } };
});
...
// jest.fnのcall数
expect(API.graphql.mock.calls.length).toBe(1);
fetchのモック https://www.leighhalliday.com/mock-fetch-jest
vueコンポーネント(.vue)をテストする場合
ここから Vue Test Utileの機能
とにかくcreateLocalVueでlocalVueを作って、mountかshallowMountでマウントしてwrapperを作る。
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Todo from '@/Todo.vue'
const localVue = createLocalVue(); // 何もuseとかしていないクリーンなVue
describe('...', () => {
it('...', () => {
const wrapper = shallowMount(Todo,{localVue}) // wrapperを作る。
...
})
})
・mountは普通のマウント、shallowMountは子コンポーネントがスタブに置き換えられるマウント。
・wrapperはvue componentをラップしたもので、vueに便利関数が備わったもの。
・localVueはvue.use(…)みたいな読み込みをしていないクリーンなvue
vue test utilを使った場合
text
htmlに文字列が含まれているか。text
expect(wrapper.text()).toMatch("aaa")
setValue
inputに値をセットする。
・setValueはinputに値をセットしてイベントを起こしてmodelに通知する
let input = wrapper.findComponent({ ref: "userNameInput" }).find("input");
input.setValue("aaa");
methodsのテスト
・methodsは.vmから生やせる。
・wrapper.vmを使うとプロパティとmethodsにアクセスできる
wrapper.vm.doSomeWork()
methodsはwrapperからもモックを設定できる。
let init = jest.fn();
let wrapper = mount(AAAComponent, {localVue,methods : {init}}
・非推奨のエラーが出るが、以下書くことで出ないようにできる。
import { config } from '@vue/test-utils'
config.showDeprecationWarnings = false
データ
// マウントするときにセット
mount(Component, {
data(){
return { count : 0}
}
})
// setDataなどでもセットできる。メモ:watchは起動しない
await wrapper.setData({ count: 10 })
wrapper.vm.count // vmからアクセスできる
// データを設定して画面に表示されるかのテスト。
it("...", async () => {
await wrapper.setData({ siteName: "aaa" }); // 画面に反映されるのを待つため await する。
expect(wrapper.find("#siteName").text()).toMatch("aaa");
});
プロパティ
// マウントするときにpropsDataにセットする方法
mount(Component, {
propsData: {
foo: 'some value'
}
})
// setPropsでセットできる。
await wrapper.setProps({ foo: 'bar' })
wrapper.vm.foo
// または
wrapper.props().foo
emitが起動したかのテスト
expect(wrapper.emitted("event名")).toBeTruthy(); // 起動していたら {event名 : 中身} が返る のでtoBeTruthyでチェックできる
created() mounted()等のモック
簡単のなのは中の関数をモックする方法しかないっぽい。
// こういうコードがあったとして
created(){
init()
}
// 埋め込む
let init = jest.fn();
let wrapper = mount( AAAComponent , { mocks : {init} })
$rootなど親コンポーネントのモック
parentComponentに設定する
const Parent = {
data() {
return {
value : 1
};
},
};
wrapper = mount(Target, {
localVue,
parentComponent: Parent,
});
componentの検索
import Bar from './Bar.vue'
expect(wrapper.findComponent(Bar).exists()).toBe(true);
<router-lin>のスタブ
https://vue-test-utils.vuejs.org/api/components/#routerlinkstub
import { mount, RouterLinkStub } from '@vue/test-utils'
const wrapper = mount(Component, {
stubs: {
RouterLink: RouterLinkStub
}
})
expect(wrapper.find(RouterLinkStub).props().to).toBe('/some/path')
vue パターン
基本形
import { createLocalVue, mount } from "@vue/test-utils";
import AAA from "../AAA.vue";
let localVue = createLocalVue();
describe("AAA", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(AAA, {
localVue,
});
});
describe("test", () => {
it("test", () => {
expect(wrapper).not.toBeNull();
});
});
});
Parentを含む
import { createLocalVue, mount } from "@vue/test-utils";
import Badge from "../Badge.vue";
let localVue = createLocalVue();
const Parent = {
data() {
return {};
},
};
describe("Badge", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(Badge, {
localVue,
parentComponent: Parent,
});
});
describe("test", () => {
it("test", () => {
expect(wrapper).not.toBeNull();
});
});
});
routerとstore をモック
// push
const $router = { push: jest.fn() };
// state user
const $store = { state: { user: { attributes: {} } } };
// state getters
let $store = {
getters: { isSignIn: jest.fn(() => true) },
};
...
// mockの仕方
let wrapper = mount(..., {... , mocks : { $router, $store } }
// apiをモック
import { API } from "@aws-amplify/api";
jest.mock("@aws-amplify/api", () => {
return { API: { graphql: jest.fn({}) } };
});
import { Auth } from "@aws-amplify/auth";
jest.mock("@aws-amplify/auth", () => {
return { Auth: { updateUserAttributes: jest.fn() } };
});
mock.calls.countのパターン
const $router = { push: jest.fn() };
import { Auth } from "@aws-amplify/auth";
jest.mock("@aws-amplify/auth", () => {
return { Auth: { updateUserAttributes: jest.fn() } };
});
const $store = { state: { user: { attributes: {} } } };
describe("UserNameSettingForm", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(UserNameSettingForm, {
localVue,
mocks: {
$router,
$store,
},
});
});
.....
it(....
await wrapper.setData({ userName: "" });
await wrapper.vm.$nextTick();
await wrapper.find("button").trigger("click");
expect(Auth.updateUserAttributes.mock.calls.length).toBe(1);
expect($router.push.mock.calls.length).toBe(1);
その他
子コンポーネントが読み込まれていない、などとエラーが出た場合、子コンポーネントをモックする。
jest.mock("@/AAA.vue",() => ({
name: "AAA",
render: h => h("div")
}))
methodsを使うとdeprecatedとエラーが出るので、以下のコードで抑制する。
import { config } from '@vue/test-utils'
config.showDeprecationWarnings = false