1年間単体テストを書き続けた現場から送る Vue Component のテスト / Vue Component Test

1年間単体テストを書き続けた現場から送る Vue Component のテスト / Vue Component Test

Vue Fes Japan 2018 の 1年間単体テストを書き続けた現場から送る Vue Component のテスト
https://vuefes.jp/

00e1bdf82f62888d4be62eee4fb99156?s=128

Kazuyoshi Tsuchiya

November 03, 2018
Tweet

Transcript

  1. ౔԰࿨ྑ(.01FQBCP *OD 7VF'FT+BQBO ೥ؒ୯ମςετΛ ॻ͖ଓ͚ͨݱ৔͔ΒૹΔ 7VF$PNQPOFOUͷςετ

  2. IUUQTUTVDIJLB[VOFU (.0ϖύϘ&$ࣄۀ෦ςΫχΧϧϦʔυ ౔԰࿨ྑ!UTVDIJLB[V ୲౰αʔϏεΧϥʔϛʔϦϐʔτ

  3. None
  4. None
  5. None
  6. $PNQPOFOUͷςετ ॻ͍͍ͯΔਓ 

  7. Α͘ฉ͘੠ 6*ͷςετ͸มߋ͕ൃੜ͠΍͘͢ ίετʹݟ߹Θͳ͍ΜͩΑͶ େࣄͳϩδοΫ͸$PNQPOFOUͷ ֎ʹग़ͯ͠ςετ͍ͯ͠Δ͔Β ͍Βͳ͍͔ͳͱ

  8. ͦ͏͸ݴͬͯ΋ ͔ͩΒ͔ͬ͠Γͱ ಈ࡞֬ೝ͠Α͏ ςετ͕ͳ͍ͱ ҙਤ͠ͳ͍มߋ͕ى͖ͳ͍͔ෆ҆ʜ

  9. ָͰ͖Δͱ͜Ζ͸ָͯ͠ ෆ҆ղফ͍͖͍ͯͨ͠

  10. ͦͷͨΊʹ ݱ৔Ͱ΍͍ͬͯΔ͜ͱ

  11. w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏΍ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB

  12. ԿΛςετ͢Δʁ

  13. ֎෦͔ΒݟͨৼΔ෣͍Λ ςετ͢Δ

  14. DPNQPOFOUͷςετ΋ ͦ͏Ͱͳ͍ςετͱ ߟ͑ํ͸Ұॹ

  15. !UXBEB 2ϓϥΠϕʔτϝιουͷϢχοτςετ ͸ॻ͔ͳ͍΋ͷʁ ϓϥΠϕʔτϝιουͷϢχοτςετ͸ॻ͔ͳ͍΋ͷʁ2"!*5 IUUQTRBBUNBSLJUDPKQR "୹͘·ͱΊΔͱɺϓϥΠϕʔτͳϝιο υͷςετΛॻ͘ඞཁ͸ແ͍ͱߟ͍͑ͯ· ͢ɻ தུ ϓϥΠϕʔτϝιου͸࣮૷ͷৄࡉͰ

    ͋ΓɺࣗಈςετͷλʔήοτͱͳΔʮ֎ ෦͔ΒݟͨৼΔ෣͍ʯͰ͸͋Γ·ͤΜɻ
  16. ૊৫ʹςετΛॻ͘จԽΛࠜ෇͔ͤΔઓུͱઓज़IUUQTTQFBLFSEFDLDPNUXBEB TUSBUFHZBOEUBDUJDTPGCVJMEJOHBVUPNBUFEUFTUJOHDVMUVSFJOUPPSHBOJ[BUJPO TMJEF

  17. $PNQPOFOUͰ͍͏ͱʁ

  18. 1VCMJD*OUFSGBDF $PNQPOFOU -JGFDZDMF 7VFY 4UBUF 1SPQT 6TFS *OUFSBDUJPO

  19. 0VUQVU 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU

  20. 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU -JGFDZDMF 7VFY 4UBUF 1SPQT

    6TFS *OUFSBDUJPO 7VF/:$$PNQPOFOU5FTUTXJUI7VFKT.BUU0$POOFMM:PV5VCFIUUQTXXXZPVUVCFDPNXBUDI W0*QG855IS, ςετλʔήοτ
  21. NPVOUPSTIBMMPXNPVOU $PNQPOFOU NPVOU $IJME $PNQPOFOU $PNQPOFOU $IJME $PNQPOFOU TIBMMPXNPVOU <template>

    <div> <h1>Parent</h1> <child/> </div> </template> <template> <div> <h1>Parent</h1> <!-- <child/> --> </div> </template>
  22. wجຊతʹNPVOU wʮ֎෦͔ΒΈͨৼΔ෣͍ʯ wʮઃܭͷՄಈҬΛ֬อʯ wର৅DPNQPOFOU wSPVUFS͔ΒಡΈࠐΉ͍ΘΏΔ1BHF $PNQPOFOU wෳࡶͳPSڞ௨Ͱ࢖༻͢Δ$PNQPOFOU w1BHF$PNQPOFOUͰΧόʔ͕໘౗ ࠓͷϓϩδΣΫτͰ͸

  23. w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏΍ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB

  24. Ͳ͏΍ͬͯςετ͢Δʁ

  25. +FTU .PDIB ,BSNB 7VF5FTU6UJMT *TUBOCVM $IBJ QPXFSBTTFSU 4JOPO Α͘࢖͏ςετπʔϧͷ ໾ׂΛ੔ཧ͠Α͏

  26. NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦ؀ڥ "TTFSUJPO ࣮ϒϥ΢β KTEPN ,BSNB .PDIB

    +FTU $PWFSBHF 4JOPO *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT $IBJQPXFSBTTFSU
  27. NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦ؀ڥ "TTFSUJPO ࣮ϒϥ΢β KTEPN ,BSNB .PDIB

    +FTU $PWFSBHF 4JOPO $IBJQPXFSBTTFSU *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT ࣮ϒϥ΢β͕ ඞཁͳͱ͖ ࣮ϒϥ΢β͕ ෆཁͳͱ͖
  28. -JGFDZDMF ʹର͢Δςετίʔυ

  29. -JGFDZDMFͷςετ 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU -JGFDZDMF

  30. wΑ͘ॲཧ͕ॻ͔ΕΔMJGFDZDMFIPPL wDSFBUFE wNPVOUFE w/VYUͳͲ443͍ͯ͠Δ৔߹ wBTZOD%BUB wGFUDI -JGFDZDMFͷςετ

  31. ࣮૷ྫ -JGFDZDMF created () { this.loadPosts() }, methods: { ...mapActions(['fetchPosts']),

    async loadPosts () { this.message = 'ಡΈࠐΈத...' try { await this.fetchPosts() } finally { this.message = ‘' } } }, .FUIPE ݺͼग़͠
  32. ࣮૷ྫ -JGFDZDMF created () { this.loadPosts() }, methods: { ...mapActions(['fetchPosts']),

    async loadPosts () { this.message = 'ಡΈࠐΈத...' try { await this.fetchPosts() } finally { this.message = ‘' } } }, දࣔมߋ
  33. ࣮૷ྫ -JGFDZDMF created () { this.loadPosts() }, methods: { ...mapActions(['fetchPosts']),

    async loadPosts () { this.message = 'ಡΈࠐΈத...' try { await this.fetchPosts() } finally { this.message = ‘' } } }, "DUJPO EJTQBUDI
  34. ςετྫ -JGFDZDMF // ϩʔΧϧͳVueίϯετϥΫλʹVuexΛΠϯετʔϧ const localVue = createLocalVue() localVue.use(Vuex) beforeEach(()

    => { // fetchPostsΞΫγϣϯͷಈ࡞֬ೝͷͨΊͷVuexपΓͷઃఆ actions = { fetchPosts: sinon.stub() // fetchPostsΞΫγϣϯͷϞοΫ } store = new Vuex.Store({ actions }) wrapper = mount(Posts, {store, localVue }) }) ͓ܾ·Γ
  35. ςετྫ -JGFDZDMF // ϩʔΧϧͳVueίϯετϥΫλʹVuexΛΠϯετʔϧ const localVue = createLocalVue() localVue.use(Vuex) beforeEach(()

    => { // fetchPostsΞΫγϣϯͷಈ࡞֬ೝͷͨΊͷVuexपΓͷઃఆ actions = { fetchPosts: sinon.stub() // fetchPostsΞΫγϣϯͷϞοΫ } store = new Vuex.Store({ actions }) wrapper = mount(Posts, {store, localVue }) }) TUPSFͷϞοΫΛ ࡞੒
  36. ςετྫ -JGFDZDMF it('loadingදࣔ͞ΕΔ͜ͱ', async () => { expect(wrapper.text()).to.contain('ಡΈࠐΈத...') }) it('fetchPosts͕dispatch͞ΕΔ͜ͱ',

    () => { sinon.assert.calledOnce(actions.fetchPosts) }) it('dispatch੒ޭ͢Δͱɺloadingද͕ࣔফ͑Δ͜ͱ', async () => { actions.fetchPosts.resolves() await flushPromises() expect(wrapper.text()).not.to.contain('ಡΈࠐΈத...') })
  37. w࣮ࡍʹ͸ɺදࣔʹඞཁͳσʔλऔಘͷͨΊʹ EJTQBUDI͢Δ͙Β͍ w͜ΕΛςετͯ͠΋ಘΒΕΔ҆৺ײ͸௿͍ wଞͷςετΛॻ͍ͨ΄͏͕Α͍ w-JGFDZDMFIPPL͸NPDLԽͯ͠ɺଞͷςετ ͷअຐʹͳΒͳ͍Α͏ʹ͢Δͷ΋ͭͷख -JGFDZDMFͷςετΛͯ͠Έͯ

  38. w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏΍ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB

  39. 1SPQT7VFY4UBUF ʹର͢Δςετίʔυ

  40. 1SPQT7VFY4UBUFͷςετ )5.- $44 $PNQPOFOU 7VFY 4UBUF 1SPQT

  41. ࣮૷ྫ දࣔ <template> <div v-if="posts.length === 0">·ͩ౤ߘ͸͋Γ·ͤΜ</div> <table v-else> <thead>

    <tr> <td>λΠτϧ</td> <td>಺༰</td> </tr> </thead> <tbody> <tr v-for="post in posts" :key="post.id"> <td>{{post.title}}</td> <td>{{post.body}}</td> </tr> </tbody> </table> </template> ݅ͷͱ͖ දࣔΛม͑Δ
  42. ςετྫ ୯७ͳBTTFSU describe('౤ߘ͕0݅ͷ৔߹', () => { beforeEach(() => wrapper.setProps({posts: []})

    it('0݅ͷද͕ࣔ͞Ε͍ͯΔ͜ͱ', () => { expect(wrapper.text()).to.contain('·ͩ౤ߘ͸͋Γ·ͤΜ') }) }) describe('౤ߘ͕1݅Ҏ্ͷ৔߹', () => { const post = {id: 1, title: ‘λΠτϧ’, body: ‘body’} beforeEach(() => wrapper.setProps({posts: [post]}) it('౤ߘͷ಺༰͕දࣔ͞Ε͍ͯΔ͜ͱ', () => { expect(wrapper.text()).to.contain(post.title) }) })
  43. w FYQFDU XSBQQFSUFYU UPDPOUBJO b·ͩ౤ߘ ͸͋Γ·ͤΜ`  wදࣔมΘͬͨΒɺςετ΋௚͢ w FYQFDU

    XSBQQFSUFYU UPDPOUBJO QPTUYY  w Ͳ͜ʹදࣔ͞Ε͍ͯΔʁͬ͘͟Γ͗͢͠ wࡉ͔ͨ͘͠Βϝϯςେม ୯७ͳBTTFSUͷͭΒ͞ ˠ͜ΕΒΛ͍͍ײ͡ʹ΍Γ͍ͨ
  44. 4OBQTIPU5FTUJOH

  45. NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦ؀ڥ "TTFSUJPO ࣮ϒϥ΢β KTEPN ,BSNB .PDIB

    +FTU $PWFSBHF 4JOPO $IBJQPXFSBTTFSU *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT ͜Εͩͱ Մೳ
  46. w DPNQPOFOUͷ%0.ʢTOBQTIPUʣΛൺֱ͢Δ w ίʔυमਖ਼લͷ%0.Λظ଴஋ͱͯ͠ɺࠓͷ%0. ͱൺֱ 4OBQTIPU5FTUJOH

  47. w ࠩ෼͕ͳ͚Ε͹TVDDFTT w ࠩ෼͕͋Ε͹Ұ౓͸GBJM w ։ൃऀ͕ࠩ෼Λ֬ೝ w ҙਤతͳมߋͰ͋Ε͹
 มߋޙͷ%0.Λ࣍ͷظ଴஋ʹ࢖͏Α͏ʹ
 VQEBUF͢Δ

    4OBQTIPU5FTUJOH
  48. w %0.ͷมߋ͸͍͍ײ͡ʹςετͰ͖ΔΑ͏ʹ ͳΔ w DPNQPOFOU͸ɺ)5.- $44 w $44ͷςετ΋͍ͨ͠ 4OBQTIPU5FTUJOHͷͭΒ͞

  49. 7JTVBM5FTUJOH

  50. w 4OBQTIPU5FTUJOHͷը૾൛ w ը૾ͷࠩ෼ݕग़πʔϧ͕ผʹඞཁ w ৭ʑͳํ๏͕͋Δ 7JTVBM5FTUJOH

  51. ࢲ͕࠾༻ͨ͠ ࠷ߴͷ7JTVBM5FTUJOHͷํ๏ ʙ1SPQT7VFY4UBUFฤʙ

  52. None
  53. w QSPQT΍7VFYTUBUFΛ༩͑ͯදࣔύλʔϯΛ֬ೝͰ͖Δ w DPNQPOFOU୯ҐͳͷͰΤοδέʔεʹ΋ରԠԽ w ؆୯ʹTDSFFOTIPU͕ͱΕΔ w ࠓ͸[JTVJΛ࢖͍ͬͯΔ 4UPSZCPPLͱ͸ IUUQTTUPSZCPPLTWVFOFUMJGZDPN

  54. 4UPSZCPPLͰQSPQTΛ౉͢ storiesOf('propsΛड͚औΔcomponent', module) .add('ۭͷͱ͖', () => ({ components: { PostsByProps

    }, template: '<posts-by-props :posts=posts/>', data: () => ({ posts: [] }) })) .add('౤ߘ͕͋Δͱ͖', () => ({ components: { PostsByProps }, template: '<posts-by-props :posts=posts/>', data: () => ({ posts: [{id: 1, title: ‘title', body: 'ϘσΟ'}] }) }))
  55. 4UPSZCPPLͰ7VFY4UBUFΛ౉͢ storiesOf('vuex storeΛड͚औΔcomponentྫ', module) .add('ۭͷͱ͖', () => ({ components: {

    PostsByStore }, template: '<posts-by-store />', store: new Vuex({ state: () => ({ posts: [] }) }) })) .add('౤ߘ͕͋Δͱ͖', () => ({ components: { PostsByStore }, template: '<posts-by-store />', store: new Vuex({ state: () => ({ posts: [{id: 1, title: 'λΠτϧ1', body: 'ϘσΟ'}] }) }) }))
  56. w7VFYʹґଘ͍ͯ͠ΔDPNQPOFOU͸TUPSFΛ४උ͢ Δͷ͕໘౗ w͚ͩͲɺ7JTVBM5FTU͍ͨ͠$PNQPOFOU w1SFTFOUBUJPOBMͱ$POUBJOFS$PNQPOFOUʹ෼ ͚Δ w൚༻తͳϞοΫTUPSFΛ४උͯ͠ɺશͯͷ DPNQPOFOUͰ࢖͍ճ͢ wࠓͷॴɺͬͪ͜Ͱ΍ͬͯΔ 4UPSZCPPLॻ͍ͯΈͯ

  57. ݱ৔ͷίʔυ import { state, getters, mutations, actions } from ‘@/store'

    function createStubStore () { const store = new Vuex.Store({state, getters, mutations, actions}) sinon.stub(store, 'dispatch').resolves() store.state.posts = [{id: 1, title: 'λΠτϧ1', body: 'ϘσΟ'}] // ࣮ࡍ͸͜͜ͷstate४උ͕͍ͬͺ͍͋Δ return store } const emptyPostsStore = createStubStore() emptyPostsStore.state.posts = [] storiesOf('Posts', module) .add('ۭͷͱ͖', () => ({ components: { Posts }, template: '<posts/>', store: emptyPostsStore, })) ຊ෺ͷTUPSFΛϕʔεʹ EJTQBUDINFUIPEͱ TUBUFΛϞοΫԽ͢Δ
  58. ݱ৔ͷίʔυ import { state, getters, mutations, actions } from ‘@/store'

    function createStubStore () { const store = new Vuex.Store({state, getters, mutations, actions}) sinon.stub(store, 'dispatch').resolves() store.state.posts = [{id: 1, title: 'λΠτϧ1', body: 'ϘσΟ'}] // ࣮ࡍ͸͜͜ͷstate४උ͕͍ͬͺ͍͋Δ return store } const emptyPostsStore = createStubStore() emptyPostsStore.state.posts = [] storiesOf('Posts', module) .add('ۭͷͱ͖', () => ({ components: { Posts }, template: '<posts/>', store: emptyPostsStore, })) ൚༻తͳ TUPSFΛඍௐ੔ͯ͠ TUPSZ΁
  59. w7JTVBM5FTUJOHΛ։ൃϑϩʔʹࡌͤΔͷʹඞཁ ͳػೳΛඋ͍͑ͯΔπʔϧ wը૾ͷࠩ෼நग़ wࠩ෼ͷSFQPSUΛIUNMʹग़ྗͯ͠ɺT΁Ξοϓ ϩʔυ w(JUIVC΁ͷ௨஌ SFHTVJUͱ͸

  60. w$PNQPOFOUΛमਖ਼ͯ͠GFBUVSFCSBODI΁QVTI TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ IPPL QVTI

  61. JO$* TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ TDSFFOTIPU CZ[JTVJ ෼ذݩͷը૾ DPNQBSF GFBUVSFCSBODIը૾ DPNNFOU SFHTVJU

  62. TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ

  63. TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ #FGPSF "GUFS ࠩ෼ 13"QQSPWF

  64. wDPNQPOFOU୯ҐͰͷςετ͕Մೳ w։ൃϑϩʔ͕ීஈͷ։ൃͱҰॹ wTUPSZCPPL͕؆୯ wσβΠφ͞Μ΋ฤूͯ͘͠ΕΔ wແྉ w༗ঈπʔϧ΋͍ͬͺ͍͋Δ ࠷ߴϙΠϯτ

  65. wϦϑΝΫλ࣌΋ػೳ௥Ճ࣌΋ɺͱͯ΋҆৺ wϨϏϡʔґཔ΍ϨϏϡʔͷෛՙ͕Լ͕Δ w7JTVBM5FTUͷ݁Ռ͚ͩͰͳ͘ɺͦΕͱ͸ผʹ $*Ͱ13͝ͱʹTUPSZCPPL΋σϓϩΠ wϨϏϡʔ࣌ʹຖճDIFDLPVU΍ɺಈ࡞֬ೝ༻ ͷ؀ڥ͕ෆཁ 7JTVBM5FTU΍ͬͯΈͯ

  66. wΫϩεϒϥ΢βରԠ wTUPSZCPPLͷTDSFFOTIPU͸QVQQFUFFS w༗ྉαʔϏε΋ݕ౼ ͜Ε͔ΒͷվળϙΠϯτ

  67. w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏΍ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB

  68. 6TFS*OUFSBDUJPO ʹର͢Δςετίʔυ

  69. 6TFS*OUFSBDUJPO 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU 6TFS *OUFSBDUJPO

  70. wϑΥʔϜೖྗ wϘλϯΫϦοΫ wεΫϩʔϧ w%% wεϫΠϓ wϐϯνΠϯɺΞ΢τ wFUD 6TFS*OUFSBDUJPOͱ͸

  71. wϑΥʔϜೖྗ wϘλϯΫϦοΫ wεΫϩʔϧ w%% wεϫΠϓ wϐϯνΠϯɺΞ΢τ wFUD 6TFS*OUFSBDUJPOͱ͸ ςετ೉қ౓௿ ςετ೉қ౓ߴ

    ͬͪ͜ͷΈΛςετ͍ͯ͠Δ
  72. ࣮૷ྫ 6TFS*OUFSBDUJPO <template> <div> <h1>৽ن࡞੒</h1> <form class="form" novalidate > <div

    class="form-item"> <label for="title">λΠτϧ</label> <input type=“text" id="title" name="title" v-model="post.title" v-validate="'required'"> <span v-show=“errors.has('title')"> {{ errors.first('title') }} </span> </div> <div class="form-item"> <label for="body">಺༰</label> <input type=“text" id="body" name="body" v-model="post.body" v-validate="'required'"> <span v-show="errors.has('body')">{{ errors.first('body') }}</span> </div> <button type="button" id=“create-button" @click="save">อଘ</button> </form> </div> </template> JOQVUUFYUY #VUUPOY γϯϓϧͳGPSN
  73. ࣮૷ྫ 6TFS*OUFSBDUJPO <template> <div> <h1>৽ن࡞੒</h1> <form class="form" novalidate > <div

    class="form-item"> <label for="title">λΠτϧ</label> <input id="title" name="title" v-model="post.title" type=“text" v-validate="'required'"> <span v-show=“errors.has('title')"> {{ errors.first('title') }} </span> </div> <div class="form-item"> <label for="body">಺༰</label> <input id="body" name="body" v-model="post.body" type=“text" v-validate="'required'"> <span v-show="errors.has('body')">{{ errors.first('body') }}</span> </div> <button id="create-button" type="button" @click="save">อଘ</button> </form> </div> </template> WFFWBMJEBUF ͰඞਢνΣοΫ
  74. ࣮૷ྫ 6TFS*OUFSBDUJPO export default { data: () => ({ post:

    { title: '', body: '' } }), methods: { async save () { const isValid = await this.$validator.validate() if (isValid) { await this.$store.dispatch( 'createPost', { post: this.post } ) this.$router.push({ path: '/' }) } } } } WBMJEBUFͯ͠ BDUJPOEJTQBUDI
  75. ςετྫ XSBQQFS४උ // ϩʔΧϧͳVueίϯετϥΫλʹVuex/VeeValidateΛΠϯετʔϧ const localVue = createLocalVue() localVue.use(Vuex) localVue.use(VeeValidate)

    beforeEach(() => { // Vue RouterͷϞοΫઃఆ $router = { push: sinon.stub() } // createPostΞΫγϣϯͷಈ࡞֬ೝͷͨΊͷVuexपΓͷઃఆ actions = { createPost: sinon.stub() } store = new Vuex.Store({ actions }) wrapper = mount(NewPost, { mocks: { $router }, store, localVue, sync: false }) }) TUPSFͷϞοΫΛ ࡞੒NPVOU
  76. ςετྫ ೖྗͯ͠DMJDLͨ͠ͱ͖ describe('ೖྗͯ͠อଘϘλϯΛclickͨ͠ͱ͖', () => { beforeEach(async () => {

    wrapper.find('#title').setValue('title') wrapper.find('#body').setValue('body') wrapper.find('#create-button').trigger('click') await flushPromises() }) it('ೖྗ͞Εͨ಺༰Ͱaction͕dispatch͞ΕΔ͜ͱ', () => { sinon.assert.calledWithMatch(actions.createPost, {}, { post: { title: 'title', body: 'body' } }) }) }) ೖྗޙʹϘλϯDMJDLͷͱ͖ EJTQBUDI͞ΕΔ
  77. ςετྫ ೖྗͤͣʹDMJDLͨ͠ͱ͖ describe('ະೖྗͰอଘϘλϯΛclickͨ͠ͱ͖', () => { beforeEach(async () => {

    wrapper.find('#create-button').trigger('click') await flushPromises() }) it('validationΤϥʔ͕දࣔ͞ΕΔ͜ͱ', () => { expect(wrapper.text()).to.contain( 'The title field is required.') }) it('action͕dispatch͞Εͳ͍͜ͱ', () => { sinon.assert.notCalled(actions.createPost) }) }) ະೖྗͰϘλϯDMJDLͷͱ͖ WBMJEBUJPOΤϥʔදࣔEJTQBUDI͞Εͳ͍
  78. ςετྫ ೖྗͤͣʹDMJDLͨ͠ͱ͖ describe('ະೖྗͰอଘϘλϯΛclickͨ͠ͱ͖', () => { beforeEach(async () => {

    wrapper.find('#create-button').trigger('click') await flushPromises() }) it('validationΤϥʔ͕දࣔ͞ΕΔ͜ͱ', () => { expect(wrapper.text()).to.contain( 'The title field is required.') }) it('action͕dispatch͞Εͳ͍͜ͱ', () => { sinon.assert.notCalled(actions.createPost) }) }) ද͕ࣔมΘͬͨ෦෼Λ ୯७ͳBTTFSUͯ͠Δ
  79. wྫ͑͹
 WBMJEBUJPOΤϥʔৄࡉΛ։͘ด͡Δ
 Ϙλϯԡͯ͠Ϟʔμϧ։͘FUD wཁ͸ςετͷதͰɺTOBQTIPUΛࡱΓ͍ͨ 6TFS*OUFSBDUJPOޙͷը૾ΛࡱΓ͍ͨ wrapper.find('#create-button').trigger('click') screenshot(‘ະೖྗͰͷclickޙ.png’)

  80. NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦ؀ڥ "TTFSUJPO ࣮ϒϥ΢β KTEPN ,BSNB .PDIB

    +FTU $PWFSBHF 4JOPO $IBJQPXFSBTTFSU *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT ͜Εͩͱ Մೳ
  81. wLBSNBOJHIUNBSF wLBSNBͰOJHIUNBSF FMFDUSPO ্ͰςετΛ ࣮ߦͰ͖Δ΋ͷ wTDSFFOTIPU"1*Λఏڙͯ͘͠ΕΔ wը૾͕ࡱΕͨΒTUPSZCPPLͷը૾ͱҰॹʹ SFHTVJUͷϑϩʔʹࡌͤΔ 5FTUதʹTDSFFOTIPUࡱΔʹ͸

  82. w؆୯ͳ6TFS*OUFSBDUJPOͷςετ͸େม͡Όͳ͍ wॏཁͳ'PSN͚ͩͰ΋΍͓ͬͯ͘ͱ͍͍ wͦΕҎ֎͸ఘΊ΋؊৺ w6TFS*OUFSBDUJPOޙͷTDSFFOTIPUͱͬͯɺ7JTVBM 5FTU͸΍ͬͺΓศར 6TFS*OUFSBDUJPOͷςετͯ͠Έͯ

  83. ·ͱΊ

  84. 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU -JGFDZDMF 7VFY 4UBUF 1SPQT

    6TFS *OUFSBDUJPO ςετλʔήοτ
  85. ͜͜·Ͱ঺հ͖ͯͨ͠ ৭ʑͳςετΛ ݱ৔Ͱ͖ͯ͠·ͨ͠ ͦͷ݁ՌɺҰ൪࠷ߴͳςετ͸

  86. )5.- $44 $PNQPOFOU 7VFY 4UBUF 1SPQT 6TFS *OUFSBDUJPO ͜ͷ෦෼ͷ7JTVBM5FTU

  87. wTUPSZCPPLͰͷʮ1SPQT7VFY4UBUFˠදࣔʯ ͷςετ͚ͩͰ΋͋Δͱ҆৺ wʮ6TFS*OUFSBDUJPOˠදࣔʯͷςετ΋͋ Δͱߋʹ҆৺ͷ෯͕޿͕Δ w෭࡞༻ͱͯ͠ϨϏϡʔͷෛՙܰݮ w͜Ε͕ඇৗʹେ͖͍ 7JTVBM5FTU͸࠷ߴ

  88. ָͰ͖Δͱ͜Ζ͸ָͯ͠ ෆ҆Λղফͭͭ͠ ָ͘͠։ൃ͍͖ͯ͠·͠ΐ͏