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
キヨさん、67歳。AIコーディングエージェントに挑む
happysamurai294
0
130
AIエージェント統制はAPIから——押さえるべき5つの要素
nagix
0
110
バイブコーディングの特徴と「コケて直す」ループ
happysamurai294
0
140
推論基盤と Scale-Up Interconnect の現状と制約から俯瞰する技術動向
markunet
2
690
ひとりで始めた AI駆動開発、チームに広げて何が変わったのか
kenichiodo
1
200
Devin プロモコードの受け取りと適用(2026.10.27)
kinopeee
1
780
スマートロックを支えるFlutter × ネイティブ連携の実践 / Flutter and Native Integration in Practice: Powering Smart Locks
bitkey
PRO
0
130
クラウドエージェントの先駆け Devin。今も最先端を走る、他との違い
kinopeee
1
520
Agent × Maestro で始める モバイル E2E 端末準備からテスト実行・結果確認まで
sansantech
PRO
0
240
kintoneで在庫管理システムを作るときの要件定義・設計ポイント
kai0707
0
190
増え続けるプロダクトの品質をどう担保するか。ProductSpecとTypeScriptによる取り組み
joere
3
290
なぜ東京にいる私が、新潟の技術コミュニティを運営するのか by 八雲慎之助
yakumo
0
210
Featured
See All Featured
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
The Invisible Side of Design
smashingmag
301
52k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
820
Heart Work Chapter 1 - Part 1
lfama
PRO
10
37k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
780
Rails Girls Zürich Keynote
gr2m
96
14k
Tell your own story through comics
letsgokoyo
1
1.1k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
310
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
290
Building Applications with DynamoDB
mza
96
7.3k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
1.1k
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࠷ߴ
ָͰ͖Δͱ͜Ζָͯ͠ ෆ҆Λղফͭͭ͠ ָ͘͠։ൃ͍͖ͯ͠·͠ΐ͏