An Introduction to Test-Driven Development with Vue.js

Testing is one of the pillars of writing robust software.

Tests should give you confidence that you aren’t shipping broken software.

But… testing can be tough. Especially with UI components.

HTML? CSS classes? View logic? Event handlers? Methods? Computed properties? Lifecycle steps? 100% coverage? Unit vs. integration vs. e2e?

Artwork by Simon Ålander

Artwork by Simon Ålander

Test-Driven Development (TDD) Popularized by Kent Beck

1 Red Write a test that describes an expected behavior, then run it, ensuring it fails.

2 Green Write the dumbest, most straightforward code you can to make the test pass.

3 Refactor Refactor the code to make it right.

TDD

What does TDD look like with Vue?

src/components/Rating.vue 1 2 3 4 5 <template> </template> <script> </script>

tests/unit/Rating.spec.js 1 2 3 4 5 6 7 8 9 10 11 12 13 import { shallowMount } from ‘@vue/test-utils’ import Rating from ‘@/components/Rating’ let wrapper = null beforeEach(() => { wrapper = shallowMount(Rating) }) afterEach(() => { wrapper.destroy() })

Thinking time!

5 stars to display

Red

1 describe(‘Rating’, () => { 2 test(‘renders a list of stars’, () => { 3 const stars = wrapper.findAll(‘.star’) 4 expect(stars.length).toBe(5) 5 }) 6 })

Terminal npm run test:unit —watchAll

FAIL tests/unit/Rating.spec.js Rating ✕ renders a list of stars (7ms) ● Rating › renders a list of stars expect(received).toBe(expected) // Object.is equality Expected: 5 Received: 0 16 | 17 | > 18 | | 19 | test(‘renders a list of stars’, () => { const stars = wrapper.findAll(‘.star’) expect(stars.length).toBe(5) ^ })

Green

1 <template> 2 <ul> 3 <li class=”star”></li> 4 <li class=”star”></li> 5 <li class=”star”></li> 6 <li class=”star”></li> 7 <li class=”star”></li> 8 </ul> 9 </template>

PASS tests/unit/Rating.spec.js Rating ✓ renders a list of stars (21ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total

Refactor

1 2 beforeEach(() => { 3 wrapper = shallowMount(Rating, { 4 propsData: { 5 maxStars: 5 6 } 7 }) 8 })

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <ul> <li :key=”star” v-for=”star in maxStars” class=”star”></li> </ul> </template> <script> export default { props: { maxStars: { type: Number, default: 5 } } } </script>

PASS tests/unit/Rating.spec.js Rating ✓ renders a list of stars (6ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total

The whole idea of TDD is not to write code to make things work, but to make tests pass. pass.

Thinking time!

2 active stars to display

1 test(‘renders active stars with class active’, () => { 2 const activeStars = wrapper.findAll(‘.active’) 3 expect(activeStars.length).toBe(2) 4 })

FAIL tests/unit/Rating.spec.js Rating ✓ renders a list of stars (5ms) ✕ renders active stars with class active (9ms) ● Rating › renders active stars with class active expect(received).toBe(expected) // Object.is equality Expected: 2 Received: 0 24 | test(‘renders active stars with class active’, () => { 25 | const activeStars = wrapper.findAll(‘.active’) > 26 | expect(activeStars.length).toBe(2) | ^ 27 | }) 28 | })

1 <li 2 :key=”star” 3 v-for=”star in maxStars” 4 :class=”{ ‘active’: star <= 2 }” 5 class=”star”></li>

PASS tests/unit/Rating.spec.js Rating ✓ renders a list of stars (6ms) ✓ renders active stars with class active (4ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total

1 beforeEach(() => { 2 wrapper = shallowMount(Rating, { 3 propsData: { 4 maxStars: 5, 5 initialGrade: 2 6 } 7 }) 8 })

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <!— … —> <li :key=”star” v-for=”star in maxStars” :class=”{ ‘active’: star <= initialGrade }” class=”star”></li> <!— … —> </template> <script> export default { props: { // … initialGrade: { type: Number, default: 0 } } } </script>

PASS tests/unit/Rating.spec.js Rating ✓ renders a list of stars (4ms) ✓ renders active stars with class active (2ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total

What exactly are we testing?

Tests should give you confidence that you aren’t shipping broken software.

Black box testing Assert only the public interface.

Who are your users?

Final user

Developer

submit event click event props With UI components, your public interface is bigger than you think. HTML classes scroll event hover event

Do I care about this if it changes?

1 TDD is a fantastic way to write robust tests, tests, not too many and not too few.

2 TDD encourages refactors, which leads to better software design. design.

3 TDD is much easier to follow with specs. specs.

But… TDD takes a lot of time. time.

So, is it worth it?

1 With practice, you will get faster at TDD. TDD.

2 Fixing bugs is far more costly than preventing them. them.

Thank you! Questions? @frontstuff_io github.com/sarahdayan frontstuff.io/an-introduction-to-tdd-with-vuejs