Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
1年間単体テストを書き続けた現場から送る Vue Component のテスト / Vue C...
Search
Kazuyoshi Tsuchiya
November 03, 2018
Technology
27
26k
1年間単体テストを書き続けた現場から送る Vue Component のテスト / Vue Component Test
Vue Fes Japan 2018 の 1年間単体テストを書き続けた現場から送る Vue Component のテスト
https://vuefes.jp/
Kazuyoshi Tsuchiya
November 03, 2018
Tweet
Share
More Decks by Kazuyoshi Tsuchiya
See All by Kazuyoshi Tsuchiya
大量購入を支える 決済トランザクション設計 / EC Payment Transaction Architecture
tsuchikazu
5
1.9k
Other Decks in Technology
See All in Technology
乗りこなせAI駆動開発の波
eltociear
1
1.1k
Uncertainty in the LLM era - Science, more than scale
gaelvaroquaux
0
840
re:Inventで気になったサービスを10分でいけるところまでお話しします
yama3133
1
120
[JAWS-UG 横浜支部 #91]DevOps Agent vs CloudWatch Investigations -比較と実践-
sh_fk2
1
250
エンジニアとPMのドメイン知識の溝をなくす、 AIネイティブな開発プロセス
applism118
4
1.2k
チーリンについて
hirotomotaguchi
6
1.8k
Overture Maps Foundationの3年を振り返る
moritoru
0
170
ガイドラインを軸にしたウェブアクセシビリティ改善
lycorptech_jp
PRO
1
100
大企業でもできる!ボトムアップで拡大させるプラットフォームの作り方
findy_eventslides
1
710
Edge AI Performance on Zephyr Pico vs. Pico 2
iotengineer22
0
140
コミューンのデータ分析AIエージェント「Community Sage」の紹介
fufufukakaka
0
480
[CMU-DB-2025FALL] Apache Fluss - A Streaming Storage for Real-Time Lakehouse
jark
0
110
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1032
470k
Art, The Web, and Tiny UX
lynnandtonic
303
21k
Site-Speed That Sticks
csswizardry
13
1k
4 Signs Your Business is Dying
shpigford
186
22k
Done Done
chrislema
186
16k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.1k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
The World Runs on Bad Software
bkeepers
PRO
72
12k
The Invisible Side of Design
smashingmag
302
51k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Transcript
ྑ(.01FQBCP *OD 7VF'FT+BQBO ؒ୯ମςετΛ ॻ͖ଓ͚ͨݱ͔ΒૹΔ 7VF$PNQPOFOUͷςετ
IUUQTUTVDIJLB[VOFU (.0ϖύϘ&$ࣄۀ෦ςΫχΧϧϦʔυ ྑ!UTVDIJLB[V ୲αʔϏεΧϥʔϛʔϦϐʔτ
None
None
None
$PNQPOFOUͷςετ ॻ͍͍ͯΔਓ
Α͘ฉ͘ 6*ͷςετมߋ͕ൃੜ͘͢͠ ίετʹݟ߹Θͳ͍ΜͩΑͶ େࣄͳϩδοΫ$PNQPOFOUͷ ֎ʹग़ͯ͠ςετ͍ͯ͠Δ͔Β ͍Βͳ͍͔ͳͱ
ͦ͏ݴͬͯ ͔ͩΒ͔ͬ͠Γͱ ಈ࡞֬ೝ͠Α͏ ςετ͕ͳ͍ͱ ҙਤ͠ͳ͍มߋ͕ى͖ͳ͍͔ෆ҆ʜ
ָͰ͖Δͱ͜Ζָͯ͠ ෆ҆ղফ͍͖͍ͯͨ͠
ͦͷͨΊʹ ݱͰ͍ͬͯΔ͜ͱ
w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB
ԿΛςετ͢Δʁ
֎෦͔ΒݟͨৼΔ͍Λ ςετ͢Δ
DPNQPOFOUͷςετ ͦ͏Ͱͳ͍ςετͱ ߟ͑ํҰॹ
!UXBEB 2ϓϥΠϕʔτϝιουͷϢχοτςετ ॻ͔ͳ͍ͷʁ ϓϥΠϕʔτϝιουͷϢχοτςετॻ͔ͳ͍ͷʁ2"!*5 IUUQTRBBUNBSLJUDPKQR "͘·ͱΊΔͱɺϓϥΠϕʔτͳϝιο υͷςετΛॻ͘ඞཁແ͍ͱߟ͍͑ͯ· ͢ɻ தུ ϓϥΠϕʔτϝιου࣮ͷৄࡉͰ
͋ΓɺࣗಈςετͷλʔήοτͱͳΔʮ֎ ෦͔ΒݟͨৼΔ͍ʯͰ͋Γ·ͤΜɻ
৫ʹςετΛॻ͘จԽΛ͔ࠜͤΔઓུͱઓज़IUUQTTQFBLFSEFDLDPNUXBEB TUSBUFHZBOEUBDUJDTPGCVJMEJOHBVUPNBUFEUFTUJOHDVMUVSFJOUPPSHBOJ[BUJPO TMJEF
$PNQPOFOUͰ͍͏ͱʁ
1VCMJD*OUFSGBDF $PNQPOFOU -JGFDZDMF 7VFY 4UBUF 1SPQT 6TFS *OUFSBDUJPO
0VUQVU 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU
7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU -JGFDZDMF 7VFY 4UBUF 1SPQT
6TFS *OUFSBDUJPO 7VF/:$$PNQPOFOU5FTUTXJUI7VFKT.BUU0$POOFMM:PV5VCFIUUQTXXXZPVUVCFDPNXBUDI W0*QG855IS, ςετλʔήοτ
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>
wجຊతʹNPVOU wʮ֎෦͔ΒΈͨৼΔ͍ʯ wʮઃܭͷՄಈҬΛ֬อʯ wରDPNQPOFOU wSPVUFS͔ΒಡΈࠐΉ͍ΘΏΔ1BHF $PNQPOFOU wෳࡶͳPSڞ௨Ͱ༻͢Δ$PNQPOFOU w1BHF$PNQPOFOUͰΧόʔ͕໘ ࠓͷϓϩδΣΫτͰ
w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB
Ͳ͏ͬͯςετ͢Δʁ
+FTU .PDIB ,BSNB 7VF5FTU6UJMT *TUBOCVM $IBJ QPXFSBTTFSU 4JOPO Α͘͏ςετπʔϧͷ ׂΛཧ͠Α͏
NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦڥ "TTFSUJPO ࣮ϒϥβ KTEPN ,BSNB .PDIB
+FTU $PWFSBHF 4JOPO *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT $IBJQPXFSBTTFSU
NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦڥ "TTFSUJPO ࣮ϒϥβ KTEPN ,BSNB .PDIB
+FTU $PWFSBHF 4JOPO $IBJQPXFSBTTFSU *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT ࣮ϒϥβ͕ ඞཁͳͱ͖ ࣮ϒϥβ͕ ෆཁͳͱ͖
-JGFDZDMF ʹର͢Δςετίʔυ
-JGFDZDMFͷςετ 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU -JGFDZDMF
wΑ͘ॲཧ͕ॻ͔ΕΔMJGFDZDMFIPPL wDSFBUFE wNPVOUFE w/VYUͳͲ443͍ͯ͠Δ߹ wBTZOD%BUB wGFUDI -JGFDZDMFͷςετ
࣮ྫ -JGFDZDMF created () { this.loadPosts() }, methods: { ...mapActions(['fetchPosts']),
async loadPosts () { this.message = 'ಡΈࠐΈத...' try { await this.fetchPosts() } finally { this.message = ‘' } } }, .FUIPE ݺͼग़͠
࣮ྫ -JGFDZDMF created () { this.loadPosts() }, methods: { ...mapActions(['fetchPosts']),
async loadPosts () { this.message = 'ಡΈࠐΈத...' try { await this.fetchPosts() } finally { this.message = ‘' } } }, දࣔมߋ
࣮ྫ -JGFDZDMF created () { this.loadPosts() }, methods: { ...mapActions(['fetchPosts']),
async loadPosts () { this.message = 'ಡΈࠐΈத...' try { await this.fetchPosts() } finally { this.message = ‘' } } }, "DUJPO EJTQBUDI
ςετྫ -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 }) }) ͓ܾ·Γ
ςετྫ -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ͷϞοΫΛ ࡞
ςετྫ -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('ಡΈࠐΈத...') })
w࣮ࡍʹɺදࣔʹඞཁͳσʔλऔಘͷͨΊʹ EJTQBUDI͢Δ͙Β͍ w͜ΕΛςετͯ͠ಘΒΕΔ҆৺ײ͍ wଞͷςετΛॻ͍ͨ΄͏͕Α͍ w-JGFDZDMFIPPLNPDLԽͯ͠ɺଞͷςετ ͷअຐʹͳΒͳ͍Α͏ʹ͢Δͷͭͷख -JGFDZDMFͷςετΛͯ͠Έͯ
w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB
1SPQT7VFY4UBUF ʹର͢Δςετίʔυ
1SPQT7VFY4UBUFͷςετ )5.- $44 $PNQPOFOU 7VFY 4UBUF 1SPQT
࣮ྫ දࣔ <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> ݅ͷͱ͖ දࣔΛม͑Δ
ςετྫ ୯७ͳ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) }) })
w FYQFDU XSBQQFSUFYU UPDPOUBJO b·ͩߘ ͋Γ·ͤΜ` wදࣔมΘͬͨΒɺςετ͢ w FYQFDU
XSBQQFSUFYU UPDPOUBJO QPTUYY w Ͳ͜ʹදࣔ͞Ε͍ͯΔʁͬ͘͟Γ͗͢͠ wࡉ͔ͨ͘͠Βϝϯςେม ୯७ͳBTTFSUͷͭΒ͞ ˠ͜ΕΒΛ͍͍ײ͡ʹΓ͍ͨ
4OBQTIPU5FTUJOH
NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦڥ "TTFSUJPO ࣮ϒϥβ KTEPN ,BSNB .PDIB
+FTU $PWFSBHF 4JOPO $IBJQPXFSBTTFSU *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT ͜Εͩͱ Մೳ
w DPNQPOFOUͷ%0.ʢTOBQTIPUʣΛൺֱ͢Δ w ίʔυमਖ਼લͷ%0.Λظͱͯ͠ɺࠓͷ%0. ͱൺֱ 4OBQTIPU5FTUJOH
w ͕ࠩͳ͚ΕTVDDFTT w ͕ࠩ͋ΕҰGBJM w ։ൃऀ͕ࠩΛ֬ೝ w ҙਤతͳมߋͰ͋Ε มߋޙͷ%0.Λ࣍ͷظʹ͏Α͏ʹ VQEBUF͢Δ
4OBQTIPU5FTUJOH
w %0.ͷมߋ͍͍ײ͡ʹςετͰ͖ΔΑ͏ʹ ͳΔ w DPNQPOFOUɺ)5.- $44 w $44ͷςετ͍ͨ͠ 4OBQTIPU5FTUJOHͷͭΒ͞
7JTVBM5FTUJOH
w 4OBQTIPU5FTUJOHͷը૾൛ w ը૾ͷࠩݕग़πʔϧ͕ผʹඞཁ w ৭ʑͳํ๏͕͋Δ 7JTVBM5FTUJOH
ࢲ͕࠾༻ͨ͠ ࠷ߴͷ7JTVBM5FTUJOHͷํ๏ ʙ1SPQT7VFY4UBUFฤʙ
None
w QSPQT7VFYTUBUFΛ༩͑ͯදࣔύλʔϯΛ֬ೝͰ͖Δ w DPNQPOFOU୯ҐͳͷͰΤοδέʔεʹରԠԽ w ؆୯ʹTDSFFOTIPU͕ͱΕΔ w ࠓ[JTVJΛ͍ͬͯΔ 4UPSZCPPLͱ IUUQTTUPSZCPPLTWVFOFUMJGZDPN
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: 'ϘσΟ'}] }) }))
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: 'ϘσΟ'}] }) }) }))
w7VFYʹґଘ͍ͯ͠ΔDPNQPOFOUTUPSFΛ४උ͢ Δͷ͕໘ w͚ͩͲɺ7JTVBM5FTU͍ͨ͠$PNQPOFOU w1SFTFOUBUJPOBMͱ$POUBJOFS$PNQPOFOUʹ ͚Δ w൚༻తͳϞοΫTUPSFΛ४උͯ͠ɺશͯͷ DPNQPOFOUͰ͍ճ͢ wࠓͷॴɺͬͪ͜ͰͬͯΔ 4UPSZCPPLॻ͍ͯΈͯ
ݱͷίʔυ 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ΛϞοΫԽ͢Δ
ݱͷίʔυ 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
w7JTVBM5FTUJOHΛ։ൃϑϩʔʹࡌͤΔͷʹඞཁ ͳػೳΛඋ͍͑ͯΔπʔϧ wը૾ͷࠩநग़ wࠩͷSFQPSUΛIUNMʹग़ྗͯ͠ɺTΞοϓ ϩʔυ w(JUIVCͷ௨ SFHTVJUͱ
w$PNQPOFOUΛमਖ਼ͯ͠GFBUVSFCSBODIQVTI TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ IPPL QVTI
JO$* TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ TDSFFOTIPU CZ[JTVJ ذݩͷը૾ DPNQBSF GFBUVSFCSBODIը૾ DPNNFOU SFHTVJU
TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ
TUPSZCPPLSFHTVJUͷ։ൃϑϩʔ #FGPSF "GUFS ࠩ 13"QQSPWF
wDPNQPOFOU୯ҐͰͷςετ͕Մೳ w։ൃϑϩʔ͕ීஈͷ։ൃͱҰॹ wTUPSZCPPL͕؆୯ wσβΠφ͞Μฤूͯ͘͠ΕΔ wແྉ w༗ঈπʔϧ͍ͬͺ͍͋Δ ࠷ߴϙΠϯτ
wϦϑΝΫλ࣌ػೳՃ࣌ɺͱͯ҆৺ wϨϏϡʔґཔϨϏϡʔͷෛՙ͕Լ͕Δ w7JTVBM5FTUͷ݁Ռ͚ͩͰͳ͘ɺͦΕͱผʹ $*Ͱ13͝ͱʹTUPSZCPPLσϓϩΠ wϨϏϡʔ࣌ʹຖճDIFDLPVUɺಈ࡞֬ೝ༻ ͷڥ͕ෆཁ 7JTVBM5FTUͬͯΈͯ
wΫϩεϒϥβରԠ wTUPSZCPPLͷTDSFFOTIPUQVQQFUFFS w༗ྉαʔϏεݕ౼ ͜Ε͔ΒͷվળϙΠϯτ
w$PNQPOFOUͷԿΛςετ͢Δͷ͔ʁ wͲ͏ͬͯςετ͢Δʁ w-JGFDZDMFฤ w1SPQT7VFY4UBUFฤ w6TFS*OUFSBDUJPOฤ "HFOEB
6TFS*OUFSBDUJPO ʹର͢Δςετίʔυ
6TFS*OUFSBDUJPO 7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU 6TFS *OUFSBDUJPO
wϑΥʔϜೖྗ wϘλϯΫϦοΫ wεΫϩʔϧ w%% wεϫΠϓ wϐϯνΠϯɺΞτ wFUD 6TFS*OUFSBDUJPOͱ
wϑΥʔϜೖྗ wϘλϯΫϦοΫ wεΫϩʔϧ w%% wεϫΠϓ wϐϯνΠϯɺΞτ wFUD 6TFS*OUFSBDUJPOͱ ςετқ ςετқߴ
ͬͪ͜ͷΈΛςετ͍ͯ͠Δ
࣮ྫ 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
࣮ྫ 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 ͰඞਢνΣοΫ
࣮ྫ 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
ςετྫ 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
ςετྫ ೖྗͯ͠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͞ΕΔ
ςετྫ ೖྗͤͣʹ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͞Εͳ͍
ςετྫ ೖྗͤͣʹ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ͯ͠Δ
wྫ͑ WBMJEBUJPOΤϥʔৄࡉΛ։͘ด͡Δ Ϙλϯԡͯ͠Ϟʔμϧ։͘FUD wཁςετͷதͰɺTOBQTIPUΛࡱΓ͍ͨ 6TFS*OUFSBDUJPOޙͷը૾ΛࡱΓ͍ͨ wrapper.find('#create-button').trigger('click') screenshot(‘ະೖྗͰͷclickޙ.png’)
NPDLϥΠϒϥϦ ςεςΟϯά ϑϨʔϜϫʔΫ ςετϥϯφʔ ࣮ߦڥ "TTFSUJPO ࣮ϒϥβ KTEPN ,BSNB .PDIB
+FTU $PWFSBHF 4JOPO $IBJQPXFSBTTFSU *TUBOCVM $PNQPOFOU 5FTU6UJMJUZ 7VF5FTU6UJMT ͜Εͩͱ Մೳ
wLBSNBOJHIUNBSF wLBSNBͰOJHIUNBSF FMFDUSPO ্ͰςετΛ ࣮ߦͰ͖Δͷ wTDSFFOTIPU"1*Λఏڙͯ͘͠ΕΔ wը૾͕ࡱΕͨΒTUPSZCPPLͷը૾ͱҰॹʹ SFHTVJUͷϑϩʔʹࡌͤΔ 5FTUதʹTDSFFOTIPUࡱΔʹ
w؆୯ͳ6TFS*OUFSBDUJPOͷςετେม͡Όͳ͍ wॏཁͳ'PSN͚ͩͰ͓ͬͯ͘ͱ͍͍ wͦΕҎ֎ఘΊ؊৺ w6TFS*OUFSBDUJPOޙͷTDSFFOTIPUͱͬͯɺ7JTVBM 5FTUͬͺΓศར 6TFS*OUFSBDUJPOͷςετͯ͠Έͯ
·ͱΊ
7VFY "DUJPO &WFOU )5.- $44 $PNQPOFOU -JGFDZDMF 7VFY 4UBUF 1SPQT
6TFS *OUFSBDUJPO ςετλʔήοτ
͜͜·Ͱհ͖ͯͨ͠ ৭ʑͳςετΛ ݱͰ͖ͯ͠·ͨ͠ ͦͷ݁ՌɺҰ൪࠷ߴͳςετ
)5.- $44 $PNQPOFOU 7VFY 4UBUF 1SPQT 6TFS *OUFSBDUJPO ͜ͷ෦ͷ7JTVBM5FTU
wTUPSZCPPLͰͷʮ1SPQT7VFY4UBUFˠදࣔʯ ͷςετ͚ͩͰ͋Δͱ҆৺ wʮ6TFS*OUFSBDUJPOˠදࣔʯͷςετ͋ Δͱߋʹ҆৺ͷ෯͕͕Δ w෭࡞༻ͱͯ͠ϨϏϡʔͷෛՙܰݮ w͜Ε͕ඇৗʹେ͖͍ 7JTVBM5FTU࠷ߴ
ָͰ͖Δͱ͜Ζָͯ͠ ෆ҆Λղফͭͭ͠ ָ͘͠։ൃ͍͖ͯ͠·͠ΐ͏