Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
1年間単体テストを書き続けた現場から送る Vue Component のテスト / Vue C...
Search
Kazuyoshi Tsuchiya
November 03, 2018
Technology
27k
27
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
1年間単体テストを書き続けた現場から送る Vue Component のテスト / Vue Component Test
Vue Fes Japan 2018 の 1年間単体テストを書き続けた現場から送る Vue Component のテスト
https://vuefes.jp/
Kazuyoshi Tsuchiya
November 03, 2018
More Decks by Kazuyoshi Tsuchiya
See All by Kazuyoshi Tsuchiya
AIに攻撃される前に、AIに攻撃させる
tsuchikazu
0
250
大量購入を支える 決済トランザクション設計 / EC Payment Transaction Architecture
tsuchikazu
5
2k
Other Decks in Technology
See All in Technology
re:Invent の去年のスケジュールを公開します!
miu_crescent
PRO
0
130
[re:Invent2026事前勉強会(有志開催)]続きは、ラスベガスの廊下で / continue-in-the-vegas-hallway
sh_fk2
0
450
Obsidian Vault Architectures
x5gtrn
PRO
0
230
reInvent2026stby LT00 "Change your life!"
shinichirokawano
1
500
Oracle Database Security 詳細解説
oracle4engineer
PRO
1
180
Vue で書く 空間コンピューティング、TresJS と WebXR を使ったXR デバイスのフロントエンド開発
hiranuma
0
140
Don't write the architecture only in the prompt.
neohsu
0
280
これだけは抑えておきたい!後悔しない re:Invent の過ごし方 / AWS re:Invent 2026 prestudy
kinunori
2
620
慶應義塾大学 AI実践05 論文執筆の仕組み化
keio_smilab
PRO
2
2.3k
AI時代の開発はウォーターフォールに回帰する? / development-with-ai-back-to-wf
okamototakuyasr2
0
160
キヨさん67歳 AIコーディングエージェント物語
happysamurai294
0
140
AIエージェント時代、Kaggleにどう取り組むか
takanao
2
450
Featured
See All Featured
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.2k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.4k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
520
A Modern Web Designer's Workflow
chriscoyier
699
190k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.7k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
43k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
330
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
1
290
Raft: Consensus for Rubyists
vanstee
143
7.8k
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࠷ߴ
ָͰ͖Δͱ͜Ζָͯ͠ ෆ҆Λղফͭͭ͠ ָ͘͠։ൃ͍͖ͯ͠·͠ΐ͏