Click events
Great! Our method works, but that's not how our user will be interacting with the application. Let's see whether we can make our tests take into account a user input form and subsequent button:
<form @submit.prevent="addTodo(todoName)">
<input type="text" v-model="todoName">
<button type="submit">Submit</button>
</form>
We can also make a small change to ouraddTodofunction, ensuring thatthis.todosis given the value of the newtodoitems:
addTodo(name) {
this.todos = [...this.todos, { id: uuid(), name }];
return this.todos;
},
The great thing is that by making this change, we can check against all of our previous use cases and see that nothing fails! Hurray for automated testing!
Next, let's make anitblock that we can use to assert that whenever ourSubmitbutton is clicked on, an item is added:
it('should add an item to the todo list when the button is clicked', () => {
const wrapper = mount(TodoList);
})
Next, we can get the form element from within the wrapper using find, and this allows us to then trigger an event. As we're submitting a form, we'll trigger the submit event and pass along a parameter to oursubmitfunction. We can then assert that ourtodolist should be1:
it('should add an item to the todo list when the button is clicked', () => {
const wrapper = mount(TodoList);
wrapper.find('form').trigger('submit', 'Clean the car');
const todos = wrapper.vm.todos;
expect(todos.length).toBe(1);
})
We can also check to see whether the appropriate method was called when the form is submitted. Let's do that usingjest:
it('should call addTodo when form is submitted', () => {
const wrapper = mount(TodoList);
const spy = jest.spyOn(wrapper.vm, 'addTodo');
wrapper.find('form').trigger('submit', 'Clean the car');
expect(wrapper.vm.addTodo).toHaveBeenCalled();
});
Testing events
We've made a lot of progress, but wouldn't it be great if we could test events that fire between components? Let's take a look at this by creating aTodoInput, component and abstracting our form away intothiscomponent:
<template>
<form @submit.prevent="addTodo(todoName)">
<input type="text" v-model="todoName">
<button type="submit">Submit</button>
</form>
</template>
<script>
export default {
data() {
return {
todoName: ''
}
},
methods: {
addTodo(name) {
this.$emit('addTodo', name);
}
}
}
</script>
Now, ouraddTodomethod inside ofthiscomponent fires an event. Let's test that event within aTodoInput.spec.jsfile:
import { mount } from 'vue-test-utils';
import TodoInput from '../TodoInput';
describe('TodoInput.vue', () => {
it('should fire an event named addTodo with todo name', () => {
const mock = jest.fn()
const wrapper = mount(TodoInput);
wrapper.vm.$on('addTodo', mock)
wrapper.vm.addTodo('Clean the car');
expect(mock).toBeCalledWith('Clean the car')
})
});
We're introduced to a new concept inside of this method—mock. This allows us to define our own behavior and subsequently determine what the event was called with.
Whenever theaddTodoevent is fired, themockfunction is called instead. This allows us to see whether our event is being called and also ensures that the event can take a payload.
We can also ensure that theTodoListhandlesthisevent, but firstly, ensure that you've updated theTodoListto include theTodoInputform:
<template>
<div>
<h1>TodoList</h1>
<TodoInput @addTodo="addTodo($event)"></TodoInput>
<ul>
<TodoItem v-for="todo in todos" v-bind:key="todo.id" :todo="todo">{{todo.name}}</TodoItem>
</ul>
</div>
</template>
<script>
import uuid from 'uuid/v4';
import TodoItem from './TodoItem';
import TodoInput from './TodoInput';
export default {
components: {
TodoItem,
TodoInput
},
data() {
return {
todos: [],
todoName: ''
};
},
methods: {
addTodo(name) {
this.todos = [...this.todos, { id: uuid(), name }];
return this.todos;
},
},
};
</script>
<style>
ul,
li {
list-style: none;
margin-left: 0;
padding-left: 0;
}
</style>
Then, inside ourTodoList.spec.js, we can start off by importingTodoInputand then add the following:
import TodoInput from '../TodoInput';
it('should call addTodo when the addTodo event happens', () => {
const wrapper = mount(TodoList);
wrapper.vm.addTodo = jest.fn();
wrapper.find(TodoInput).vm.$emit('addTodo', 'Clean the car');
expect(wrapper.vm.addTodo).toBeCalledWith('Clean the car');
})
Apart from that, we can also ensure that the event does what it's supposed to do;so when we fire the eventitadds an item to the array and we are testing for the array length:
it('adds an item to the todolist when the addTodo event happens', () => {
const wrapper = mount(TodoList);
wrapper.find(TodoInput).vm.$emit('addTodo', 'Clean the car');
const todos = wrapper.vm.todos;
expect(todos.length).toBe(1);
});
Using Wallaby.js for a better testing experience
We can also use Wallaby.js to see the results of our unit tests in real time within our editor. It's not a free tool, but you may find it useful when creating test-driven Vue applications. Let's start off by cloning/downloading a project that already has Wallaby set up. Run the following in your Terminal:
# Clone the repository
$ git clone https://github.com/ChangJoo-Park/vue-wallaby-webpack-template
# Change directory
$ cd vue-wallaby-webpack-template
# Install dependencies
$ npm install
# At the time of writing this package is missing eslint-plugin-node
$ npm install eslint-plugin-node
# Run in browser
$ npm run dev
We can then open this up inside our editor and install theWallaby.jsextension inside our editor. You can find a list of supported editors and instructions athttps://wallabyjs.com/download/.
I'll be installing this within Visual Studio Code, which starts by searching the extensions marketplace for Wallaby:
We can then tell Wallaby about the project's configuration file (wallaby.js), by pressingCMD+SHIFT + =_on Mac or_CTRL+_SHIFT + =_on Windows. From the drop-down, click onSelect Configuration Fileand subsequently, typewallaby.js. This will allow Wallaby and Vue to work together.
To start Wallaby, we can open the configuration menu again and selectStart. We can then navigate to thetests/unit/specs/Hello.spec.jsfile, and we should see varying blocks in the line margins of the editor:
As everything is green, we know that it has passed! What if we change the implementation details of the test? Let's purposely make one or more of our tests fail:
Everything stays green apart from the'should render correct contents'block, which can be seen down the left-hand side. This is because we now have a failed assertion, but more importantly, we don't have to rerun our tests, and they're displayed instantly within our editor. No more Alt-Tab-ing between different windows to watch our test output!
Summary
This chapter saw us learn about how to appropriately test our Vue components. We learned how to follow the fail-first approach to write tests that drive our development decisions as well as how to take advantage of Wallaby.js to see the results of our tests from within our editor!
In the next chapter, we'll learn how we can combine our Vue.js applications with modern Progressive Web Application techniques, such as service workers, application manifests, and much more!