Testing Vue Component with Mocha and Chai

Previously, We have created a Toggle Botton component with Vue. You can download the complete source codes from the GitHub repository. Today, we are going to implement Unit testing for our Vue component. And we are going to use the Mocha and Chai to perform the testing. Mocha is a simple and feature-rich test framework for javascript. Before jumping directly to the codes, Make sure to add the required packages to the unit testing environment.

It’s very easy to add the testing requirement packages with the help of Vue CLI.

vue add unit-mocha

Start with First Test Case

Now go the tests/unit directory and create a file similar to your component name i.e. ToggleButton.spec.js. Of cause it’s because of our ToggleButton.vue component. Now open the test file and import the required packages.

import { shallowMount } from '@vue/test-utils'
import { expect } from 'chai'
import ToggleButton from '@/components/ToggleButton.vue'

We could use mount or shallowMount, it depends on you. The basic difference is the shallowMount mount a component without rendering its child components. The expect is a common assertion to get the desired result.

Let’s start writing our first test case for the ToggleButton component.

import { shallowMount } from '@vue/test-utils'
import { expect } from 'chai'
import ToggleButton from '@/components/ToggleButton.vue'

describe('ToggleButton', () => {

    // First Test Case
    it ('should render correctly', () => {})
});

Let’s check our test by running the following command and you will get the output like the below screenshot.

npm run test:unit

The output shows you the test case is passed. But we haven’t added any test case yet. It is just to check that all the above steps that you are doing are working fine. Now open the ToggleButton.spec.js file and attach our component with expected result.

it ('should render correctly', () => {

    let wrapper = shallowMount(ToggleButton);

    expect(wrapper.find('.toggle__label').text()).to.contain('Off');
    expect(wrapper.vm.currentState).to.be.false;
});

The very first step that we have added is mounting our component with shallowMount() and assigned it to the wrapper variable. Next, we have added two assertion cases that we expect to exists after render our component.

First, take a look at the ToggleButton component.

<template>
    <label :for="id + '_button'" :class="{'active': isActive}" class="toggle__button">
        <span v-if="isActive" class="toggle__label">On</span>
        <span v-if="! isActive" class="toggle__label">Off</span>

        <input type="checkbox" :id="id + '_button'" v-model="checkedValue">
        <span class="toggle__switch"></span>
    </label>
</template>

<script>
export default {

    data() {
        return {
            currentState: false
        }
    },

    computed: {

        isActive() {
            return this.currentState;
        },

        checkedValue: {
            get() {
                return this.currentState;
            },

            set(newValue) {
                this.currentState = newValue;
            }
        }
    }
}
</script>
  1. The first case that we expect is to have the Off label i.e. working on the behalf of currentState state.
  2. The next case that we need is the currentState should have the false value.

Both of the expected results we are achieving through the following codes.

expect(wrapper.find('.toggle__label').text()).to.contain('Off');
expect(wrapper.vm.currentState).to.be.false;

All of the to, be, false, contain, etc. are the chainable assertions and is part of the chai package. And now if you this test again then you get a similar result like previous screenshot.

Let’s verify again our test case by adding a wrong expected result i.e. the currentState state should be true ( this should fail our test ).

it ('should render correctly', () => {

    let wrapper = shallowMount(ToggleButton);

    expect(wrapper.find('.toggle__label').text()).to.contain('Off');
    expect(wrapper.vm.currentState).to.be.true;
});

And see, our test is failed because we are expected the true value of the currentState over false.

Feel like skywalker? Me too 😜

via GIPHY

TDD – Second Test Case

The currentState of the toggle button and the label should be changed when we click on the toggle button. It means the currentState value should be true and the label should be On.

it ('should change currentState on toggle', () => {

    let wrapper = shallowMount(ToggleButton);

    expect(wrapper.find('.toggle__label').text()).to.contain('Off');
    expect(wrapper.vm.currentState).to.be.false;

    wrapper.find('input[type=checkbox]').setChecked();

    expect(wrapper.find('.toggle__label').text()).to.contain('On');
    expect(wrapper.vm.currentState).to.be.true;
});

As simple as it described but we have an issue with this test case. We do not get the expected result after the setChecked() function. Of cause, the setChecked() function would set the checkbox to be checked.

The problem is not with the setChecked() function but the problem is, we are expecting the result before the currectState gets updated after setting checked. So, what is the solution for this?

We have to use the vm.$nextTick() after state change i.e. made by setChecked() function.

it ('should change currentState on toggle', () => {

    let wrapper = shallowMount(ToggleButton);

    expect(wrapper.find('.toggle__label').text()).to.contain('Off');
    expect(wrapper.vm.currentState).to.be.false;

    wrapper.find('input[type=checkbox]').setChecked();
    
    wrapper.vm.$nextTick();

    expect(wrapper.find('.toggle__label').text()).to.contain('On');
    expect(wrapper.vm.currentState).to.be.true;
});

If we run again the test codes we get the same error. It means the state still not updating or we have to wait for the state update before the next assertion.

Let’s perform this test case asynchronously by using the async / await and see what happen.

it ('should change currentState on toggle', async () => {

    let wrapper = shallowMount(ToggleButton);

    expect(wrapper.find('.toggle__label').text()).to.contain('Off');
    expect(wrapper.vm.currentState).to.be.false;

    wrapper.find('input[type=checkbox]').setChecked();
    
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.toggle__label').text()).to.contain('On');
    expect(wrapper.vm.currentState).to.be.true;
});

The line await wrapper.vm.$nextTick(); waits to resolve the state changed and once it resolved the next assertion will get processed.

Great 👏, both of the cases run successfully.

via GIPHY

We have added more cases to the related GitHub repository that you can check and learn. If you guys enjoyed this post then please share it with your friends and colleagues. And If you have queries related to these test cases then feel free to reach me out through the comment section.