(with Jest and Vue Test Utils)
What is unit testing?
The idea behind unit testing is to test functionality that can be separated into small parts (aka "units"), concentrating on their public interface and treating implementation logic as a "black box".
How does unit testing compare to other types of testing (integration, e2e)?
Fair question:
why should we bother writing unit tests if, for example, integration tests do the same job better?
Answer: costs and speed.
Are there any benefits to writing tests except the obvious?
Yes, code documentation.
Why Jest? Why not, for example, Mocha?
Are there any Jest implementation aspects (specific to Vue.js) that I should know about?
I don't want to configure Webpack by hand, it's too much hassle. Is there a simpler option?
Yes,
How should I organize file structure with unit tests in my project?
2 approaches:
1. Root folder approach
my-project
├── src
│ ├── App.vue
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
└── tests
└── unit
└── HelloWorld.spec.js2. Proximity principle approach
my-project
└── src
├── App.vue
├── components
│ ├── __tests__
│ │ └── HelloWorld.spec.js
│ └── HelloWorld.vue
└── main.jsCreating a unit test in Jest
/* utils/increment.js */
export function increment(num) {
if(typeof num !== 'number') {
throw new TypeError('num must be a number')
}
return num + 1
}
/* utils/__tests__/increment.spec.js */
import { increment } from '../increment'
test('increment returns 3 when given 2', () => {
expect(increment(2)).toBe(3)
})
test('increment throws TypeError when given "duck"', () => {
function incrementString () {
increment('duck')
}
expect(incrementString).toThrowError(TypeError)
})Handling asynchronicity
/* utils/increment.js */
export function incrementPromise(num) {
return new Promise((resolve, reject) => {
if (typeof num !== 'number') {
const error = new TypeError('num must be a number')
setTimeout(function() {
reject(error)
}, 1000)
} else {
setTimeout(function() {
resolve(num + 1)
}, 1000)
}
})
}
/* utils/__tests__/increment.spec.js */
import { incrementPromise } from '../increment'
test('incrementPromise returns 3 when given 2', () => {
incrementPromise(2).then(response => {
expect(response).toBe(3)
})
})However...
test('returns 3 when given "duck"', () => {
incrementPromise('duck').then(response => {
/* we break test intentionally... */
expect(response).toBe(3)
/* ...but it still passes. */
})
})
Test fails only if an error is thrown.
Therefore, if an error is not thrown, test passes successfully.
If the error is thrown in promise, Jest needs to know about that.
We need to return the promise.
Promise syntax
/* will break */
test('incrementPromise returns 3 when given "duck"', () => {
return incrementPromise('duck').then(response => {
expect(response).toBe(3)
})
})
/* same, but shorter */
test('incrementPromise returns 3 when given "duck"', () => {
return expect(incrementPromise("duck")).resolves.toBe(3)
})
test('incrementPromise throws an error when given "duck"', () => {
const error =
new TypeError('num must be a number')
return expect(incrementPromise('duck')).rejects.toMatchObject(error)
})async/await syntax
test('incrementPromise returns 3 when given 2', async () => {
await expect(incrementPromise(2)).resolves.toBe(3)
})
test('incrementPromise throws an error when given "duck"', async () => {
const error =
new TypeError('num must be a number')
await expect(incrementPromise('duck')).rejects.toMatchObject(error)
})Jest is a library for testing JavaScript,
Vue Test Utils is a library for testing Vue.js-specific stuff
Unit testing components in Vue.js
General idea:
render the component, and assert that the markup matches the state of the component.
// ~/components/counter/index.vue
<template>
<div>
<span class="count">{{ count }}</span>
<button @click="increment">Increment</button>
</div>
</template>
<script>
export default {
name: "counter",
data () {
return {
count: 0
};
},
methods: {
increment () {
this.count++
}
}
}
</script>// ~/components/counter/__tests__/counter.spec.js
import Counter from '..'
import { shallowMount } from '@vue/test-utils'
const wrapper = shallowMount(Counter)
describe('Counter', () => {
test('renders the correct markup', () => {
expect(wrapper.html()).toContain(
'<span class="count">0</span>'
)
})
test('has a button', () => {
expect(wrapper.contains('button')).toBe(true)
})
test('button click increments the count', () => {
const count = wrapper.vm.count
const button = wrapper.find('button')
button.trigger('click')
expect(wrapper.vm.count).toBe(count + 1)
})
})