テスト駆動開発でbuttonを追加

Vue.jsで作成した空白のwebページに、Jestを利用したテスト駆動開発でbuttonを追加をする。

- button(id=sampleButton)の存在をテスト
- buttonのラベル(サンプルのボタン)をテスト
  - buttonのラベルが変数にバインドされていない場合
  - buttonのラベルが変数にバインドされている場合
    - buttonのラベルのテスト
    - buttonのラベルがバインドされた変数のテスト
- buttonがクリックされて呼び出されるメソッド(onClick)のテスト

完成したソースコードは[GitHubのリポジトリ](https://github.com/kubotama/sample_button)にある。

# buttonを追加する前のディレクトリ構成とファイル

buttonを追加する前のディレクトリ構成

```sh
├ index.html
├ public
│  └ index.html
└ src
    ├ App.vue
    ├ components
    │ └ SampleButton.vue
    └ main.js
```

```html:public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

```javascript:src/main.js
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

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

```html:src/App.vue
<template>
  <div id="app">
    <SampleButton />
  </div>
</template>

<script>
import SampleButton from "./components/SampleButton.vue";

export default {
  name: "App",
  components: {
    SampleButton
  }
};
</script>

<style></style>
```

```html:src/components/SampleButton.vue
<template>
  <div></div>
</template>

<script>
export default {
  name: "SampleButton"
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
```

# buttonの存在を確認するテストを追加

buttonを追加するために、buttonの存在を確認するテストを追加する。

```javascript:tests/unit/button.spec.js
import { shallowMount } from '@vue/test-utils'
import SampleButton from '@/components/SampleButton.vue'

describe('追加するbuttonのidをsampleButtonとする。', () => {
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(SampleButton)
  })

  it('存在する。', () => {
    expect(wrapper.find('#sampleButton').exists()).toBeTruthy()
  })
})
```

まだbuttonは追加していないので、以下のようにエラーになる。

```sh
FAIL  tests/unit/button.spec.js
  追加するbuttonのidをsampleButtonとする。
    ✕ 存在する。 (39ms)

  ● 追加するbuttonのidをsampleButtonとする。 › 存在する。

    expect(received).toBeTruthy()

    Received: false

       9 |
      10 |   it('存在する。', () => {
    > 11 |     expect(wrapper.find('#sampleButton').exists()).toBeTruthy()
         |                                                    ^
      12 |   })
      13 | })
      14 |
```

src/components/SampleButton.vueにbuttonを追加することで、テストは成功する。

```html:src/components/SampleButton.vue(抜粋)
<template>
  <div>
   <button id='sampleButton'></button>
  </div>
</template>
```

# buttonのラベルのテストを追加

buttonのラベルを確認するテストを追加する。

```javascript:tests/unit/button.spec.js(抜粋)
  it('ラベル(サンプルのラベル)が正しい。', () => {
    expect(wrapper.find('#sampleButton').text()).toBe('サンプルのラベル')
  })
```

ラベルを設定していないので、以下のようにエラーになる。

```sh
FAIL  tests/unit/button.spec.js
  追加するbuttonのidをsampleButtonとする。
    ✓ 存在する。 (23ms)
    ✕ ラベル(サンプルのラベル)が正しい。 (6ms)

  ● 追加するbuttonのidをsampleButtonとする。 › ラベル(サンプルのラベル)が正しい。

    expect(received).toBe(expected) // Object.is equality

    Expected: "サンプルのラベル"
    Received: ""

      13 |
      14 |   it('ラベル(サンプルのラベル)が正しい。', () => {
    > 15 |     expect(wrapper.find('#sampleButton').text()).toBe('サンプルのラベル')
         |                                                  ^
      16 |   })
      17 | })
      18 |
```

buttonのラベルをtemplateのHTMLに書いてしまう方法と、ラベルをバインドした変数にJavaScriptで設定する方法がある。

## buttonのラベルを変数にバインドしない場合

ますは、templateのHTMLに書いてしまう場合には、src/components/SampleButton.vueのtemplateを修正することでテストは成功する。

```html:src/components/SampleButton.vue(抜粋)
<template>
  <div>
   <button id='sampleButton'>サンプルのラベル</button>
  </div>
</template>
```

## buttonのラベルを変数にバインドする場合

### buttonのラベルのテストを追加

ラベルをバインドした変数にJavaScriptで設定する場合は、src/components/SampleButton.vueのtemplateとscriptを修正する。

```html:src/components/SampleButton.vue(抜粋)
<template>
  <div>
    <button id="sampleButton">{{ sampleLabel }}</button>
  </div>
</template>

<script>
export default {
  name: "SampleButton",
  data() {
    return {
      sampleLabel: "サンプルのラベル"
    };
  },
  methods: {
    onClick() {
      return;
    }
  }
};
</script>
```

### buttonのラベルがバインドされた変数のテストを追加

buttonのラベルがバインドされた変数(sampleLabel)のテストを追加する。

```javascript:tests/unit/button.spec.js
it('buttonにラベルにバインドされた変数が正しい。', () => {
    expect(wrapper.vm.sampleLabel).toBe('サンプルのラベル')
  })
```

既に正しく変数にバインドされているため、テストは成功する。

# buttonがクリックされて呼び出されるメソッドのテスト

buttonがクリックされるとonClickが呼び出されることを確認するテストを追加する。

```javascript:tests/unit/button.spec.js
  it('クリックするとonClickが呼び出される。', () => {
    const onClick = jest.fn()
    wrapper.setMethods({ onClick })
    wrapper.find('#sampleButton').trigger('click')
    expect(onclick).toHaveBeenCalledTimes(1)
  })
```

onClickを呼び出すように設定していないため、以下のようにエラーになる。

```sh
FAIL  tests/unit/button.spec.js
  追加するbuttonのidをsampleButtonとする。
    ✓ 存在する。 (22ms)
    ✓ ラベル(サンプルのラベル)が正しい。 (2ms)
    ✕ クリックするとonClickが呼び出される。 (7ms)

  ● 追加するbuttonのidをsampleButtonとする。 › クリックするとonClickが呼び出される。

    TypeError: Cannot set property 'onClick' of undefined

      18 |   it('クリックするとonClickが呼び出される。', () => {
      19 |     const onClick = jest.fn()
    > 20 |     wrapper.setMethods({ onClick })
         |             ^
      21 |     wrapper.find('#sampleButton').trigger('click')
      22 |     expect(onclick).toHaveBeenCalledTimes(1)
      23 |   })
```

src/components/SampleButton.vueを以下のように修正して、クリックされたときにonCkickを呼び出すように設定することでテストに成功する。

```html:src/components/SampleButton.vue(抜粋)
<template>
  <div>
   <button id='sampleButton' @click="onClick">サンプルのラベル</button>
  </div>
</template>

<script>
export default {
  name: "SampleButton",
  methods: {
    onClick() {
      return
    }
  }
};
</script>
```

以上で、Vue.jsで作成するwebページにテスト駆動でbuttonを追加した。