Vue Test Utils と Jest でコンポーネントのユニットテストを書く際のTips

## はじめに
Vueでコンポーネントの単体テストを書くにあたっての、やり方のメモを書き残します。

誰かのお役に立てれば幸いです。

私が過去に書いたJestの記事も参照してくださいね。

JestでVueコンポーネントとVuexストアの単体テストを書いてみよう!
https://qiita.com/karamage/items/e8757282e49e81f4d4eb

## Vue Test Utils ガイドをよく読もう

Vueの単体テストを書くにあたって、初心者だし、何もわからんって人は、
まずは「Vue Test Utils ガイド」を読むと良いです。

このガイドはとてもわかり易いです。
https://vue-test-utils.vuejs.org/ja/guides/

## VuexのMock化のやりかた

Vuexを使っているコンポーネントの単体テストを書く際は、Vuexをモック化する必要があります。
以下に、その際のテンプレートを貼っておきます。

```test.js
import { shallowMount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import HogeCell from "@/components/HogeCell.vue";

const localVue = createLocalVue();

localVue.use(Vuex);

describe("HogeCellのテスト", () => {
  let store;
  let hogeStoreMock;
  let wrapper;
  const selectedHoge = {
    id: 12345,
    name: "からまげ",
  };
  beforeEach(() => {
    // Vuexストアのモックを作成する
    hogeStoreMock = {
      namespaced: true,
      actions: {
        setHoge: jest.fn(),
        setFuga: jest.fn()
      },
      getters: {
        selectedHoge: () => selectedHoge
      }
    };
    store = new Vuex.Store({
      modules: {
        hoge: hogeStoreMock
      }
    });
    // shallowMountだと子コンポーネントをスタブによって描画しなくなる(高速化)
    wrapper = shallowMount(HogeCell, {
      store,
      localVue,
      propsData: {
        hoge: selectedHoge
      }
    });
  });

  test("textに含まれていること", () => {
    expect(wrapper.find(".hoge_fuga").text()).toMatch("hogehoge");
  });
  
  // ... 略
});
```

### Element UIを使うコンポーネントのテストのやりかた
```js
import { shallowMount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import ElementUI from 'element-ui'; 

const localVue = createLocalVue();

localVue.use(Vuex);
localVue.use(ElementUI);

describe("test", () => {
...略
```

### カスタムディレクティブのモック化のやりかた
```js
   wrapper = shallowMount(HogeCell, {
      // 略
      directives: {
        hogeHogeDirective() {}
      }
    });
```

## アラートのテストの書き方

```test.js
  test("名前の入力がない状態で送信ボタンを押した際、アラートが表示され、送信ができないことを確認する", async () => {
    await wrapper.setData({ name: "" });
    const mock = jest.fn();
    window.alert = jest.fn();
    wrapper.setMethods({ post: mock });
    await wrapper.vm.tapSend();
    expect(mock).not.toBeCalled();
    expect(window.alert).toBeCalled();
  });
```

## LocalStorageのMock化のやりかた
```test.js
class LocalStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = value.toString();
  }

  removeItem(key) {
    delete this.store[key];
  }
}

global.localStorage = new LocalStorageMock();
describe("HogeForm", () => {
  // 略

  beforeEach(() => {
    global.localStorage.setItem("HOGE_KEY", "hogehoge");
    // 略
  })
})
```

##  オフラインの場合のテストのNavigatorのモック化のやりかた

```js
test('should log "offline"', () => {
    const logSpy = jest.spyOn(console, 'log');
    jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(false);
    main();
    expect(logSpy).toBeCalledWith('offline');
  });
```

## 非同期処理やnextTickのテストのやりかた

```js
it('test', async() => {
  const wrapper = shallowMount(Foo);
  await flushPromises(); // mount時に非同期処理がある場合、待つ
  expect(wrapper.vm.text).toBe('マウント時のテキスト');

  wrapper.find('button').trigger('click'); // クリックイベントで非同期処理を実行

  await wrapper.vm.$nextTick(); // DOMの更新を保証
  expect(wrapper.find('text').text()).toBe('クリック後のテキスト');
})
```

## window.location.href のモック化

```js

global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, 'location', {
  value: {
    href: url
  }
});
expect(window.location.href).toEqual(url);  
```

## モック関数の呼び出し回数を取得したい場合
```js
  test("テスト", async () => {
    await flushPromises();
    expect(
      hogeStoreMock.actions.getHoge.mock.calls.length
    ).toBe(1);
    await wrapper.setData({ isLast: false });
    wrapper.vm.next();
    await flushPromises();
    expect(
      hogeStoreMock.actions.getHoge.mock.calls.length
    ).toBe(2);
  });
```

## ユーザーの入力値のテストの書き方

```js
it("フォームを更新するとお知らせを表示", () => {
  const wrapper = shallowMount(FormSubmitter, {
    mocks: {
      $http: mockHttp
    }
  })

  wrapper.find("[data-username]").setValue("karamage")
  wrapper.find("form").trigger("submit.prevent")

  expect(wrapper.find(".message").text())
    .toBe("karamageさん、お問い合わせ、ありがとうございます。")
})
```

## watch内の関数を呼び出したい場合

```js
wrapper.vm.$options.watch.bar.call(wrapper.vm)
```

## Circle CI 上での jest テストが not enough memory となる場合の対処 

テストいっぱい書くと、プロセスが立ち上がりすぎてメモリ不足で失敗することがある。
https://qiita.com/takewell/items/7b7731ffbfcbc5403efd

## カバレッジの詳細を見る方法

./coverage/lcov-report/index.html  を開くと詳細が見れる。

![スクリーンショット 2020-12-24 17.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/6d8de063-d7d3-9ee1-e0a4-86e6072c4fd0.png)