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.9k
Other Decks in Technology
See All in Technology
AWS IAM Identity Centerによる権限設定をグラフ構造で可視化+グラフRAGへの挑戦
ykimi
2
270
Raycast AI APIを使ってちょっと便利なAI拡張機能を作ってみた
kawamataryo
1
250
今日から使える AWS Step Functions 小技集 / AWS Step Functions Tips
kinunori
4
340
AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド
zozotech
PRO
2
340
設計に疎いエンジニアでも始めやすいアーキテクチャドキュメント
phaya72
28
19k
Boxを“使われる場”にする統制と自動化の仕組み
demaecan
0
200
[AWS 秋のオブザーバビリティ祭り 2025 〜最新アップデートと生成 AI × オブザーバビリティ〜] Amazon Bedrock AgentCore で実現!お手軽 AI エージェントオブザーバビリティ
0nihajim
2
370
Playwrightで始めるUI自動テスト入門
devops_vtj
0
180
最近読んで良かった本 / Yokohama North Meetup #10
mktakuya
0
980
AIの個性を理解し、指揮する
shoota
3
640
Snowflakeとdbtで加速する 「TVCMデータで価値を生む組織」への進化論 / Evolving TVCM Data Value in TELECY with Snowflake and dbt
carta_engineering
1
170
Design and implementation of "Markdown to Google Slides" / phpconfuk 2025
k1low
1
260
Featured
See All Featured
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.2k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
31
2.7k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
2.9k
Writing Fast Ruby
sferik
630
62k
For a Future-Friendly Web
brad_frost
180
10k
Docker and Python
trallard
46
3.6k
Making the Leap to Tech Lead
cromwellryan
135
9.6k
Code Review Best Practice
trishagee
72
19k
Product Roadmaps are Hard
iamctodd
PRO
55
11k
Mobile First: as difficult as doing things right
swwweet
225
10k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
950
Why You Should Never Use an ORM
jnunemaker
PRO
60
9.6k
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࠷ߴ
ָͰ͖Δͱ͜Ζָͯ͠ ෆ҆Λղফͭͭ͠ ָ͘͠։ൃ͍͖ͯ͠·͠ΐ͏