Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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.8k
Other Decks in Technology
See All in Technology
MCPを利用して自然言語で3Dプリントしてみよう!
hamadakoji
0
1.4k
Contract One Engineering Unit 紹介資料
sansan33
PRO
0
6.3k
メルカリにおけるデータアナリティクス AI エージェント「Socrates」と ADK 活用事例
na0
16
8.7k
型システムを知りたい人のための型検査器作成入門
mame
14
3.4k
Test Smarter, Not Harder: Achieving Confidence in Complex Distributed Systems
eliasnogueira
1
150
Text-to-SQLの評価データセットを作って最新LLMモデルの性能評価をしてみた
gotalab555
3
740
バクラクのモノレポにおける AI Coding のための環境整備と {Roo,Claude} Code活用事例 / AI Coding in Bakuraku's Monorepo: Environment Setup & Case Studies with {Roo, Claude} Code
upamune
8
5.4k
Two-Tower モデルで実現する 検索リランキング / Shibuya_AI_2
visional_engineering_and_design
2
170
プロジェクトマネージャーに最後まで残るたった一つの仕事は交渉
ichimichi
1
200
20250612_GitHubを使いこなすためにソニーの開発現場が取り組んでいるプラクティス.pdf
osakiy8
1
550
Nonaka Sensei
kawaguti
PRO
3
560
Kafka vs. Pulsar: Performance Evaluation by Petabyte-Scale Streaming Platform Providers
lycorptech_jp
PRO
1
350
Featured
See All Featured
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
657
60k
Why You Should Never Use an ORM
jnunemaker
PRO
56
9.4k
Product Roadmaps are Hard
iamctodd
PRO
53
11k
A designer walks into a library…
pauljervisheath
206
24k
VelocityConf: Rendering Performance Case Studies
addyosmani
329
24k
Become a Pro
speakerdeck
PRO
28
5.4k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
32
2.3k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
46
9.6k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
2.8k
Writing Fast Ruby
sferik
628
61k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
52
2.8k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
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࠷ߴ
ָͰ͖Δͱ͜Ζָͯ͠ ෆ҆Λղফͭͭ͠ ָ͘͠։ൃ͍͖ͯ͠·͠ΐ͏