Vue.js+TypeScript+Element-UIの単体テストで気をつけること

## 概要

Vue.jsを利用するのにElementってコンポーネントライブラリが便利です。

Element
http://element.eleme.io/#/en-US

Vue.jsのコンポーネント詰め合わせ「Element」がスゴかった
https://s8a.jp/vue-js-library-element

今回は、Elementを利用した独自コンポーネントを作成して、単体テストを書いてた際に気を付けたほうがよい点をまとめてみます。


## 準備

Vue-Cliで環境を作ります。

GitHubに利用したプロジェクトをUPしています。実際に試してみたい方どうぞ^^
https://github.com/kai-kou/vue-js-typescript-element-ui


ここではDockerを利用して環境構築していますが、ローカルで構築してもらってもOKです。

```sh
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> vi Dockerfile
> vi docker-compose.yml
```

```txt:Dockerfile
FROM node:10.8.0-stretch

RUN npm install --global @vue/cli

WORKDIR /projects
```

```yaml:docker-compose.yml
version: '3'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ".:/projects"
    tty: true
```

```sh
> docker-compose up -d
> docker-compose exec app bash
```

単体テストも書くので、Unit Testを忘れずに。

```sh:コンテナ内
> vue create app

Vue CLI v3.0.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter, Unit
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Pick a linter / formatter config: TSLint
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Mocha
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
Use NPM
```

```sh:コンテナ内
> cd app
> yarn serve
```

これで実装の準備が整いました。


## Elementのインストールと設定

Elementを利用できるようにする手順です。


```sh:コンテナ内
> yarn add element-ui
```

インストールできたらmain.tsに設定を追加します。

```sh
> vi src/main.ts
```

```ts:src/main.ts
import Vue from 'vue';
import App from './App.vue';

import locale from 'element-ui/lib/locale';
import lang from 'element-ui/lib/locale/lang/ja';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false;

locale.use(lang);

new Vue({
  render: h => h(App)
}).$mount('#app');

```
このままだと、下記のようなエラーがでてしまうので、メッセージにあるとおり、```.d.ts``` ファイルを作成して回避します。

```
  ERROR in /projects/app/src/main.ts
  5:18 Could not find a declaration file for module 'element-ui/lib/locale/lang/ja'. '/projects/app/node_modules/element-ui/lib/locale/lang/ja.js' implicitly has an 'any' type.
    Try `npm install @types/element-ui` if it exists or add a new declaration (.d.ts) file containing `declare module 'element-ui';`
```

```sh
> touch src/type.d.ts
> vi src/type.d.ts
```

```ts:src/type.d.ts
declare module 'element-ui';
declare module 'element-ui/lib/locale';
declare module 'element-ui/lib/locale/lang/ja';

```

## すべてのコンポーネントを読み込む場合

以下のようにするとElementの全コンポーネントが読み込めるのですが、今回は一部コンポーネントを読み込む設定にしています。
設定が簡単で良いっちゃ良いのですが、すべてのコンポーネントを利用するのでなければ、余分にコンポーネントを読み込むことになるのと、単体テストの際に困ることになるので、個人的には避けておいたいところです。

```ts:src/main.ts(全コンポーネントを読み込む場合)
import Vue from 'vue';
import App from './App.vue';

import ElementUI from 'element-ui';
import lang from 'element-ui/lib/locale/lang/ja';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false;

Vue.use(ElementUI, { lang });

new Vue({
  render: h => h(App)
}).$mount('#app');

```

## コンポーネントの利用

次にEmelentを利用するコンポーネントを追加してみます。

```sh
> touch src/components/UseElement.vue
> vi src/components/UseElement.vue
```

```vue:src/components/UseElement.vue
<template>
  <el-input placeholder="Please input" v-model="input">
  </el-input>
</template>

<script lang="ts">
  import {
    Component,
    Vue,
  } from 'vue-property-decorator';
  import {
    Input,
  } from 'element-ui';
  
  Vue.use(Input);
  @Component
  export default class UseElement extends Vue {
    private input: string = '';
  }
</script>

```

ポイントしてはコンポーネントの登録方法ですが、こちらは単体テストを追加する際に説明します。

では、追加したコンポーネントをApp.vueで利用してみます。

```sh
> vi src/App.vue
```

```vue:src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <use-element />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
import UseElement from './components/UseElement.vue';

@Component({
  components: {
    HelloWorld,
    UseElement,
  },
})
export default class App extends Vue {}
</script>
(略)
```

ブラウザで確認してみます。

<img width="1277" alt="スクリーンショット 2018-08-29 15.00.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/48549/49c55b68-758f-2974-531b-8fddd333473b.png">

はい。


## 単体テストの注意点

次に単体テストを追加します。

```sh
> touch tests/unit/UseElement.spec.ts
> vi tests/unit/UseElement.spec.ts
```


```ts:tests/unit/UseElement.spec.ts
import { expect } from 'chai';
import { shallowMount } from '@vue/test-utils';
import UseElement from '@/components/UseElement.vue';
import { Input } from 'element-ui';

describe('UseElement.vue', () => {
  it('コンポーネントが表示されるか', () => {
    const wrapper = shallowMount(UseElement, {});

    expect(wrapper.findAll(Input)).to.length(1);
  });
});

```

```sh:コンテナ内
> yarn test:unit
 WEBPACK  Compiled successfully in 32410ms

 MOCHA  Testing...

  HelloWorld.vue
    ✓ renders props.msg when passed (150ms)

  UseElement.vue
    ✓ コンポーネントが表示されるか

  2 passing (273ms)

 MOCHA  Tests completed successfully

Done in 98.04s.
```


はい。

で、コンポーネント追加時の注意点に戻るのですが、せっかくvue-class-componentを利用しているので、```@Component``` を利用してコンポーネントを登録したいのですが、それだと単体テスト時にワーニングがでてしまいます。
試してみます。

```vue:src/components/UseElement.vue(抜粋)
- Vue.use(Input);
  @Component({
-   components: {},
+   components: {
+     Input,
+   },
  })
```

これでテストを走らせてみます。

```sh:コンテナ内
> yarn test:unit

 WEBPACK  Compiled successfully in 47207ms

 MOCHA  Testing...

  HelloWorld.vue
    ✓ renders props.msg when passed (98ms)

  UseElement.vue
[Vue warn]: Unknown custom element: <el-input> - did you register the component correctly? 
For recursive components, make sure to provide the "name" option.

found in

---> <UseElement> at src/components/UseElement.vue
       <Root>
    1) コンポーネントが表示されるか

  1 passing (1s)
  1 failing

  1) UseElement.vue
       コンポーネントが表示されるか:

      AssertionError: expected { Object () } to have a length of 1 but got 0
      + expected - actual

      -0
      +1

      at Context.<anonymous> (dist/webpack:/tests/unit/UseElement.spec.ts:10:1)

 MOCHA  Tests completed with 1 failure(s)
```

悲しいかな、エラーになります。

```sh
[Vue warn]: Unknown custom element: <el-input> - did you register the component correctly? 
For recursive components, make sure to provide the "name" option.
```


```@Component``` で登録するとタグ名が反映されずエラーになるようです。
回避できなくはないのですが、自前でタグ名を指定しなきゃ駄目なので微妙です。
Element側で対応してくれるのを待つ感じでしょうか。

```vue:src/components/UseElement.vue(抜粋)
  @Component({
    components: {
-     Input,
+     'el-input': Input,
    },
  })
```

## コンポーネント登録の注意点

もう1点注意点として、main.tsでElementの全コンポーネントを登録することも可能なのですが、コンポーネントで登録をしないと単体テストでコンポーネントが登録されていないエラーが出てしまいます。


```ts:src/main.ts
import Vue from 'vue';
import App from './App.vue';

+import ElementUI from 'element-ui';
-import locale from 'element-ui/lib/locale';
import lang from 'element-ui/lib/locale/lang/ja';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false;

+Vue.use(ElementUI, { lang });
-locale.use(lang);

new Vue({
  render: (h) => h(App),
}).$mount('#app');

```


```vue:vue:src/components/UseElement.vue
<template>
  <el-input placeholder="Please input" v-model="input">
  </el-input>
</template>

<script lang="ts">
  import {
    Component,
    Vue,
  } from 'vue-property-decorator';

  @Component
  export default class UseElement extends Vue {
    private input: string = '';
  }
</script>

```

こうすると、```yarn serve``` では問題ないですが、テストすると、エラーになります。

```sh:コンテナ内
> yarn test:unit

 WEBPACK  Compiled successfully in 35529ms

 MOCHA  Testing...

  HelloWorld.vue
    ✓ renders props.msg when passed (69ms)

  UseElement.vue
[Vue warn]: Unknown custom element: <el-input> - did you register the component correctly? 
For recursive components, make sure to provide the "name" option.

found in

---> <UseElement> at src/components/UseElement.vue
       <Root>
    1) コンポーネントが表示されるか

  1 passing (687ms)
  1 failing

  1) UseElement.vue
       コンポーネントが表示されるか:

      AssertionError: expected { Object () } to have a length of 1 but got 0
      + expected - actual

      -0
      +1

      at Context.<anonymous> (dist/webpack:/tests/unit/UseElement.spec.ts:10:1)

 MOCHA  Tests completed with 1 failure(s)
```


なので、単体テスト書かねーぞって以外は、利用するコンポーネントを都度、```Vue.use``` で登録するのが良さそうです^^


## 参考

Element
http://element.eleme.io/#/en-US

Vue.jsのコンポーネント詰め合わせ「Element」がスゴかった
https://s8a.jp/vue-js-library-element

Vue.js+TypeScriptで開発するときの参考記事まとめ
https://qiita.com/kai_kou/items/19b494a41023d84bacc7