Help us understand the problem. What is going on with this article?

子コンポーネントからの通知をテストしてみた話

はじめに

前回の記事でAtomコンポーネントのテスト自動化を行う事ができました。
次はAtomとかを組み合わせたMoleculesレイヤーのコンポーネントのテストやろ!
ということでやってみました。

こんなのをテストしてみよう

Vue.js

parent.vue
<template>
    <child @click = "emitted">
    </child>
</template>

<script>
import child from '@/child.vue'
export default {
    name:"parent",
    methods:{
        emitted(value){
            this.$emit('click',value)
        }
    }
}
</script>
child.vue
<template>
    <button @click = "onClick">
</template>

<script>
export default {
    name:"atom-button",
    methods:{
        onClick(){
            this.$emit('click')
        }
    }
}
</script>

今回はさっくりこういうファイルを対象にやってみましょう。
Moleculesといいながら、parent.vueにはchild.vueが一つ配置してあるだけですが…。
機能としては、ボタンをクリックしたら子から親へ、親から更に上位へ通知が連鎖されるようにしております。
ということで、テストのほうもさっくり書いていきましょう。

parent.spec.js
import { shallowMount } from '@vue/test-utils'
import parent from '@/components/parent.vue'
import child from '@/components/child.vue'

describe('parent.vue',()=>{
    it('ボタンをクリックしたら親へイベントを通知しているか',()=>{
        const warrper = shallowMount(parent)
    })
})

ベースはこう書いておきます。
さて、今回のテスト内容ですが、
ユーザーの操作と同じようなボタンをクリックしたら果たして親からイベントが通知されるのか
をテストしてみたいと思います。

どうテストする?

単純に親のMethodであるemittedを呼び出す事で、親へイベントを通知は行う事ができます。
ただし、これではユーザーの操作と同じような流れのテストを行う事はできません。
単純に子であるボタンコンポーネントのクリックイベントを行う…のは親からは厳しいですし、
それだと単一ファイルのテストではなくなってしまいます。
ではどうするか、

warrper.vm.$emit()

を使い、子の通知を擬似的に作成する方法で試したいと思います。

parent.spec.js
describe('parent.vue',()=>{
    it('ボタンをクリックしたら親へイベントを通知しているか',()=>{
        const warrper = shallowMount(parent)
        const TEXT = "Vue.js"
        warrper.find(child).vm.$emit('click',TEXT)
        expect(warrper.emitted().click[0][0]).toBe(TEXT)
    })
})

$emitの使い方はVueの時と同じですね。
第一引数に親でキャッチするイベント名を、第二引数に送りたい値を書きます。
そして、emittedですでに発生したイベントをキャッチし、toBeで値の検証を行っております。
yarn test:unitを打ち込み、テストを実行してみますと…
9474a91c11e41187cdcce8e4635566e8.png

成功しました!

複数あった場合は?

では複数ボタンがあった場合は?
当然ながら、コンポーネントによってはボタンが複数存在し、それぞれ紐付いたMethodが違う場合もあります。

parent.vue
<template>
    <div>
        <child @click = "emitted1">
        </child>
        <child @click = "emitted2">
        </child>
        <child @click = "emitted3">
        </child>
        <child @click = "emitted4">
        </child>
        <child @click = "emitted5">
        </child>
    </div>

</template>

<script>
import child from '@/components/child.vue'
export default {
    name:"parent",
    components:{
        child
    },
    methods:{
        emitted1:function(value){
            return this.$emit('click',value)
        },
        emitted2:function(value){
            const string = value + "!"
            return this.$emit('click',string)
        },
        emitted3:function(value){
            const string = value + "!!"
            return this.$emit('click',string)
        },
        emitted4:function(value){
            const string = value + "!!!"
            return this.$emit('click',string)
        },
        emitted5:function(value){
            const string = value + "!!!!"
            return this.$emit('click',string)
        }
    }
}
</script>

先程の親コンポーネントを変更してみました。
これで先程のテストを少し改良し、実行をやってみましょう。

parent.spec.js
describe('parent.vue',()=>{
    it('クリックしたら親へイベントを通知しているか',()=>{
        const warrper = shallowMount(parent)
        const TEXT = "Vue.js"
        warrper.find(child).vm.$emit('click',TEXT)
        expect(warrper.emitted().click[0][0]).toBe(TEXT)
    })
})

b0c8c96f7f64d7d8a5462370bb56037e.png

成功しました!

2個目のテストをやってみよう

では、2個目のボタンをクリックしてみましょう!

……
………
どうクリックするの?

当然ながらfindはどの言語でもそうですが最初にヒットした要素が対象になるので、2個目を指定することはできません。
そこで、このfindAllを使います!
ただし、このままでは使えません。
なぜなら、これはArrayで取得されるため、インデックスを指定しないといけないからです。
では、よくあるインデックスの指定方で取ってみましょう!

parent.spec.js
describe('parent.vue',()=>{
    it('クリックしたら親へイベントを通知しているか',()=>{
        const warrper = shallowMount(parent)
        const TEXT = "Vue.js"
        const btn = warrper.findAll(child)[1]
        btn.vm.$emit('click',TEXT)
        expect(warrper.emitted().click[0][0]).toBe(TEXT)
    })
})

エラーが出ました。
4f812e702f5263899d5ee3c08f8d8cb2.png
vmが存在しないのでプロパティを読めねぇよ!と言われてます
つまり、[1]では配列から値を取れないということになります。
これは困りました、どう配列から取るのか…。
と、いうことで公式ガイドを見ましょう。

……
………
どうやら、at(n)でインデックスを指定し対象の値を取るようです。
早速やってみましょう。

変えてみた
parent.spec.js
describe('parent.vue',()=>{
    it('クリックしたら親へイベントを通知しているか',()=>{
        const warrper = shallowMount(parent)
        const TEXT = "Vue.js"
        const btn = warrper.findAll(child).at(1)
        btn.vm.$emit('click',TEXT)
        expect(warrper.emitted().click[0][0]).toBe(TEXT)
    })
})

実行してみましょう!
be91362441799e018f72194fb68e7697.png
エラーが出ていますね。
内容を見てみるとemittedで通知された値と、比較したい値が違うといっています。
Vue.js!emitted2のメソッドで実行される場合のみの文字列なため、2個目のボタンが押されていることになります。
つまり2個目のを指定することに成功しました!
やったー!

おわり

子のイベントを疑似的に発生させたい場合は、

warrper.vm.$emit()

を使います。
2個以上同じコンポーネントがあり、それぞれテストしたい場合には、
findAllで当てはまるものをすべて取得し、at(n)でインデックスを指定する。

 warrper.findAll(hogehoge).at(n)

今回はこれらを知り、身につけることができました。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away