Tips for Unit Testing Vue Components with Jest

Achhunna Mali
May 19 · 6 min read

Using Jest to unit test Vue.js components can be tricky. We need a separate Vue Test Utils (@vue/test-utils) scoped package in order to virtually mount our components and use Jest to execute the tests. Because these two libraries work together, it’s important to make sure we don’t get confused about which API calls belong to which library. Along with these libraries, we also need to pay attention to JSDOM (virtual browser environment) specific methods, which comes with Jest. Having to juggle all this may be confusing and can discourage us from writing unit tests.

E.g., shallowMount() is a Vue Test Utils method to create a shallow wrapper component (using shallow rendering to avoid rendering children components), and beforeEach() is a Jest method which executes the callback argument before each of the tests. We run shallowMount() inside beforeEach() so a component is mounted before every test.

I made a list of common testing methods that my team used to help successfully unit test our Vue components. Hope this will help others going through similar process.


Initial Setup

Let’s start by setting up Jest. Install Jest npm package as devDependencies

$ npm i jest -D

Now let’s install Vue Test Utils and other dependencies like babel-jest, vue-jest etc. (this is straight from the Vue Test Utils docs)

$ npm i @vue/test-utils vue-jest babel-jest -D

Once we have all packages setup, create a Jest config file jest.config.js in the root folder of our project. As an alternative, we can also add JSON object inside module.exports to a jest: {} property to package.json which cuts down on the number of config files we have to manage.

Make sure .babelrc file has config below with babel-present-env module installed.

Once we have setup the configs with options (mainly collectCoverageFrom source path) we are ready to run Jest!

Now all we need to do is add scripts: { “test”: “jest” } to package.json. Then run that script.

$ npm run test

Writing Test Files

The above script is not very useful until we have test files that Jest can run, so let’s add one.

Jest will pick up *.test.js or *.spec.js files in our project folder. I like to place test files in the same folder as my Vue components and use component file name. e.g. button.vue component will have button.test.js test file in components/ folder. This helps me manage component files and test files since they live the same folder and are right next to each other in the Explorer in VSCode.

Test File Template

Here is a base template test file I use to get started. Note shallowMount() has various properties like propsData, mocks, stubs, methods we can set to our mounted component to mock or stub various properties of the component. Common practice is to invoke shallowMount() and store mocked component in a wrapper before every test and destroy wrapper after each test. That way we are starting with a fresh state every test, which makes the test more predictable.

Jest has describe(), test() and expect() testing functions to setup each test and asset expected value. I like to check wrapper.isVueInstance test passes in the beginning to make sure I have mocked default properties for the component.

Sample Jest test template

If our component uses Vuex, we need to add Vuex to a Vue instance (named localVue here) using use() and pass the instance into shallowMount().

Template with Vuex mocks

Common Testing Methods

Let’s look at some testing methods that cover most types of unit testing.

  • Existence of DOM elements We begin the test by checking if wrapper has all default elements we expect to render when component is mounted. We do this by checking for actual element tag or other attributes like class, id etc. Jest’s assertion functions like toBe() along with wrapper.contains() or wrapper.findAll().length from Vue Test Utils can help with this test.

We can also check for innerHTML of elements using .text() function of the DOM element that wrapper.find() returns. e.g. expect(wrapper.find('.blah').text()).toBe('blah text').

  • DOM action events We can test the events that should emit when an action is performed in the component. e.g. when a close button is clicked, a dialog should be close by listening to a close event. We use trigger() function from Vue Test Utils to trigger an event on the wrapper’s selected DOM element and wrapper.emitted() gives a list of emitted events which is used to check for existence desired event.

Above we trigger click event on closeBtn DOM selector. We can also trigger other events like keydown for keypress etc.

  • Accessing Vue wrapper properties In case we need to access or change data, computed, methods and props of our Vue component, we can usewrapper.vm object since Vue Test Utils creates an instance of the component in wrapper. This is helpful when we have to mock values for one of the properties.
wrapper.vm.name = 'test'; // changes name data propertywrapper.vm.save(); // invokes save() methodwrapper.setProps({ propsName: newValue }); // changes propsName to newValue

One exception is computed properties, which need to be remounted using shallowMount() and passed in as a property. Since we mount our component in beforeEach(), we can store the shallowMount() in a factory function which can be easily called during beforeEach() and in test that needs to pass in overrides for computed properties.

  • Mocking methods and modules One of the important things to know when writing unit tests is how to fake or mock the execution of a module or method that are not part of the unit we are testing. This helps make our tests more predictable and also avoids any external code changes from failing our tests. We can use Jest’s fn() and mock() functions to return mocked versions. There is also a mocks property inside shallowMount() in Vue Test Utils which is use to mock any global functions that our component might be using without import statement.

Let’s look at mocking functions.

Mocking methods using jest.fn()

Above test, we use jest.fn() to mock the return value of various functions. We can pass an argument inside fn(() => true)that gets invoked for the mock. If left empty, an undefined function is returned.

We also mocked window.open() global function because JSDOM doesn’t provide but our test needs to execute it.

These functions are also spies which lets us spy on their behavior and use expect().toBeCalled() functions to verify they are invoked in the test. We can also use jest.spyOn(Object, function name) Jest function for similar behavior.

Other helpful function mocks include setInterval and setTimeout since Javascript native timers don't work as expected in Jest. We can use Jest’s functions like advanceTimersByTime(x) or runOnlyPendingTimers() to clear all timers, just make sure to declare jest.userFakeTimers() at the top of test file.

jest.useFakeTimers(); // Declare at top of test filejest.advanceTimersByTime(1000); // Inside test function if you have specific millisjest.runOnlyPendingTimers(); // Fast-forward until all pending timers have been executed

Mocking imported module is quite simple.

Mocking modules using jest.mock()

If we have a Constants module imported in our component, then we return the mocked object with any properties our component uses, e.g. Constants.myConstant.

  • Async callbacks If we have asynchronous code in the code block of our component, we can use async/await to await the async callback, usually a Promise.

Demo

Link to vue-jest-unit-test repo I setup with an App component along with its tests.


Conclusion

If we have a good handle of Jest and Vue Test Utils APIs, then writing unit tests for our Vue components will seem like a breeze, we may even enjoy it! And that’s important because unit testing our components helps us write better designed and easily refactorable code.

The Startup

Medium's largest active publication, followed by +469K people. Follow to join our community.

Achhunna Mali

Written by

Thoughts on technology & design, achhunna.com

The Startup

Medium's largest active publication, followed by +469K people. Follow to join our community.