About the cover illustration
6. Write an end-to-end test
4.1. TESTING PUBLIC AND PRIVATE COMPONENT METHODS
4.1.1. Testing public component methods
The process of testing public methods is simple: you call the component method and assert that the method call affected the component output correctly.
Imagine you have a pop-up component that exposes a hide method.
When the hide method is called, the component should have its
style.display property set to none. To test that the hide method worked correctly, you would mount the component, call the hide
method, and check that the component has a style.display value of none, as shown in the following code.
Listing 4.3. Testing a public method
test('is hidden when hide is called', () => {
const wrapper = shallowMount(Popup) 1
wrapper.vm.hide() 2 expect(wrapper.element.style.display).toBe('none') 3 })
1 Mounts the component
2 Calls the public hide method on the component instance
3 Asserts that the wrapper root element has a display none style
This is how you test public methods: you call the method and assert that the component output is correct. The tests you write for the
ProgressBar component will follow this pattern.
The specs for ProgressBar follow:
ProgressBar should display the bar when start is called.
ProgressBar should set the bar to 100% width when finish is called.
ProgressBar should hide the bar when finish is called.
ProgressBar should reset the width when start is called.
These tests will be pretty simple, because they are self-contained methods without any dependencies. The first test will check that the root element removes a hidden class when start is called. You can check that it doesn’t have a class by using the Vue Test Utils classes method and the Jest not modifier. The not modifier negates an assertion as follows:
expect(true).not.toBe(false)
You have an existing test that checks that the bar initializes with a
hidden class. You can replace that with the new test that checks that it initializes with a hidden class and removes it when start is called.
Add the code from the next listing to src/components /__tests__/ProgressBar.spec.js.
Listing 4.4. Testing component state
test('displays the bar when start is called', () => { const wrapper = shallowMount(ProgressBar)
expect(wrapper.classes()).toContain('hidden') 1
wrapper.vm.start() 2
expect(wrapper.classes()).not.toContain('hidden') 3
})
1 Asserts that the hidden class exists
2 Triggers the test input by calling the start method on the component instance
3 Asserts that the hidden class was removed
When you run the test with npm run test:unit, the test will display an error because start is not a function. This isn’t one of those friendly assertion errors that you know and love; it’s a useless type error.
Type errors don’t aid you in the quest for a failing assertion. You should stop type errors like this by adding boilerplate code to the unit you’re
testing. In src/-components/ProgressBar.vue add a <script> block with a methods object of empty methods in the component options, as
follows:
<script>
export default { methods: { start() {}, finish() {}
} }
</script>
Now run the test again with npm run test:unit to check that it fails with a descriptive assertion error. When you’ve seen an assertion error, you can make the test pass by adding the code from the next listing to src/components/ProgressBar.vue.
Listing 4.5. ProgressBar.vue
<template>
<div
:class='{ 1 hidden: hidden
}'
:style='{
'width': '0%' }'/>
</template>
<script>
export default { data() {
return {
hidden: true
} },
methods: { 2 start () {
this.hidden = false 3 },
finish() {}
} }
</script>
1 Defines a dynamic hidden class that is added to element if hidden is true
2 Defines the component methods
3 Sets the instance state inside the start method
Check that the test passes: npm run test:unit. Great—now the ProgressBar root element removes the hidden class when start is called. Next you want to test that calling finish sets the progress bar width to 100% and hides the bar. This will make the progress bar appear to finish loading and then disappear.
You’ll write two tests—one to check that the width is set to 100% and one to check that the hidden class is added. You can use the same approach that you did in the earlier test—call the method and then assert against the rendered output. Add the following code to
src/components/__tests__/ProgressBar.spec.js.
Listing 4.6. Testing public methods
test('sets the bar to 100% width when finish is called', ()
=> {
const wrapper = shallowMount(ProgressBar)
wrapper.vm.start() 1 wrapper.vm.finish() 2 expect(wrapper.element.style.width).toBe('100%') 3 })
test('hides the bar when finish is called', () => { const wrapper = shallowMount(ProgressBar)
wrapper.vm.start() wrapper.vm.finish()
expect(wrapper.classes()).toContain('hidden') })
1 Puts the component in a dirty state by calling start
2 Triggers the test input by calling the finish method on the component instance
3 Asserts that the element has a width of 100%
Check that the tests fail with an assertion error. To make the tests pass, you need render the width using a percent value and reset the state in the finish method.
To do that, you can add a percent property to the component and use it to render the width. In src/components/ProgresBar.vue, update the data method to return an object with percent set to 0 using the following code:
data() { return {
hidden: true, percent: 0
} },
In the same file, update the width style in the <template> block to use the percent value as follows:
'width': `${percent}%`
Finally, replace the finish method in src/components/ProgressBar.vue with the following code:
finish() {
this.hidden = true this.percent = 100 }
Make sure the tests pass: npm run test:unit.
Now you have a finish method that sets the width to 100% and a start method that will start the component running from 0%. The component is going to start and finish multiple times during the
application lifecycle, so start should reset the ProgressBar to 0%
when it’s called.
You can check this by calling the finish method to set the width to 100%, and then calling the start method and asserting that the width is reset to 0%.
Add the following code to the describe block in
src/components/__tests__/ProgressBar.spec.js:
test('resets to 0% width when start is called', () => { const wrapper = shallowMount(ProgressBar)
wrapper.vm.finish() wrapper.vm.start()
expect(wrapper.element.style.width).toBe('0%') //
})
The test will fail because finish sets the width to 100 and start doesn’t reset it. To make the test pass, you need to update the
ProgressBar start method. Add the following code to the start method in src/components/ProgressBar.vue:
this.percent = 0
Run the test again: npm run test:unit. The test will pass—nice.
These are the kind of tests I love—small, self-contained, and easy to understand. With tests like these, you’re free to refactor the
implementation of the methods however you like, as long as the component maintains its contract and generates the correct output.
As you can see, these kinds of tests are nice and simple: provide an input, call a method, and then assert the rendered output. The real complexity of testing methods is when methods have dependencies, like timer functions.
To test that the progress bar increases in width over time you need to learn how to test timer functions.