vueでtestメモ

2021-01-03IT記事,Vue

インストール

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