Write and test your first Vue.js component with Jest

Write and test your first Vue.js component with Jest

6 mins read
Oct 31, 2025
Share
editor-page-cover
Content
How to create a vue-test project
How to test a Vue component
Keep the learning going.
Disadvantage of nested components
What is shallow rendering?
How to test a Vue component with shallow rendering
Setting up your Vue 3 + Vite project with Vitest & @vue/test-utils
Behavior-first testing with Vue Testing Library
Shallow mount vs mount: When to choose which
Testing components that use Pinia or Vue Router
Pinia
Vue Router
Handling asynchronous components with flushPromises & nextTick
Migrating from Jest to Vitest (optional note)
Wrapping up and Resources
Continue Reading

The official Vue.JS testing library and based on Avoriaz, vue-test-utils, is just around the corner. It’s a great tool as it provides all necessary tooling for making quick to write unit tests in a VueJS application.

Jest, developed by Facebook, is a great companion testing framework which makes testing your programs a breeze.

Below are just a few of its awesome features:

  • Almost no config by default
  • Very cool interactive mode
  • Run tests in parallel
  • Spies, stubs and mocks out of the box
  • Built in code coverage
  • Snapshot testing
  • Module mocking utilities

Below we’ll explore just how easy testing becomes with these tools by building and testing a Vue project step-by-step.

By the end, you’ll see why so many developers have thrown away traditional testing strategies in favor of Jest.



Advance your front-end skills by learning Jest

Learn all you need to start testing Vue components quickly and easily.

Testing Vue.js Components with Jest


How to create a vue-test project#

Let’s start by creating a new project using vue-cli. When prompted, answer “NO” to all yes/no questions.

C++
npm install -g vue-cli
vue init webpack vue-test
cd vue-test

Then, we’ll need to install some dependencies.

C++
# Install dependencies
npm i -D jest vue-jest babel-jest

jest-vue-preprocessor allows Jest to understand .vue files, and babel-jest for the integration with the transcompiler software, Babel.

Next, install vue-test-utils from the package manager, npm.

C++
npm i -D vue-test-utils

Now that we have all our tools working, add the following Jest configuration in the package.json.

C++
...
"jest": {
"moduleNameMapper": {
"^vue$": "vue/dist/vue.common.js"
},
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
}
}
...

The moduleFileExtensions will tell Jest which extensions to look for, and transform determines which preprocessor to use for a file extension.


How to test a Vue component#

Note on Single File Restriction

I’ll be using Single File Components for this example, if split to seperate files results may vary.

Below, we’ll begin adding a test script to our package.json.

To add our test, first create a MessageList.vue component under src/components.

C++
<template>
<ul>
<li v-for="message in messages">
{{ message }}
</li>
</ul>
</template>
<script>
export default {
name: 'list',
props: ['messages']
}
</script>

Then update your App.vue to look like so. This will complete our program so we can move on to a test folder.

C++
<template>
<div id="app">
<MessageList :messages="messages"/>
</div>
</template>
<script>
import MessageList from './components/MessageList'
export default {
name: 'app',
data: () => ({ messages: ['Hey John', 'Howdy Paco'] }),
components: {
MessageList
}
}
</script>

We have already a couple of components that we can test. Let’s create a test folder under the project root, and a App.test.js.

C++
import Vue from "vue";
import App from "../src/App";
describe("App.test.js", () => {
let cmp, vm;
beforeEach(() => {
cmp = Vue.extend(App); // Create a copy of the original component
vm = new cmp({
data: {
// Replace data value with this fake data
messages: ["Cat"]
}
}).$mount(); // Instances and mounts the component
});
it('equals messages to ["Cat"]', () => {
expect(vm.messages).toEqual(["Cat"]);
});
});

Right now, if we run npm test (or npm t as a shorthand version), the test should run and pass. Since we’re modifying the tests, let’s better run it in watch mode:

C++
npm t -- --watch

Keep the learning going.#

Perfect the Vue testing skills sought after by recruiters and managers alike. With Educative’s interactive, developer-authored courses, you can get hands on experience with testing techniques used by industry developers today.

Testing Vue.js Components with Jest


Disadvantage of nested components#

While a great start, this test is currently too simple.

Let’s change our rest to check that the output is the expected as well. For that we can use the amazing Snapshots feature of Jest, that will generate a snapshot of the output and check it against in the upcoming runs.

Add code below after the previous it in your App.test.js.

C++
it("has the expected html structure", () => {
expect(vm.$el).toMatchSnapshot();
});

Running the test now will create a test/__snapshots__/App.test.js.snap file.

Let’s open it and inspect it.

C++
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `
<div
id="app"
>
<ul>
<li>
Cat
</li>
</ul>
</div>
`;

Another good step forward, but there is a big problem here: the MessageList component has been rendered as well.

This demonstrates a key takeaway: unit tests must be tested as an independent unit.

In other words, in App.test.js we want to test the App component and don’t care at all about anything else.

This can lead to several problems. Imagine for example, that the children components (MessageList in this case) performed side effect operations on the created hook, such as calling fetch, which causes state changes.

This would mean that our test would alter our component to succeed, breaking repeat executions.

Luckily, Shallow Rendering solves this nicely.



What is shallow rendering?#

Shallow Rendering is a technique that assures your component is rendered without children. This is useful for:

  • Testing only the component you want to test and leaving other intact
  • A void side effects that children components can have, such as making HTTP calls, calling store actions, etc.

Shallow rendering works by stubbing the components key, the render method and the lifecycle hooks, all behind the scenes.


How to test a Vue component with shallow rendering#

vue-test-utils provide us with Shallow Rendering among other features. Look below to see how we can apply shallow rendering in our test script.

C++
import { shallowMount } from "@vue/test-utils";
import App from "../src/App";
describe("App.test.js", () => {
let cmp;
beforeEach(() => {
cmp = shallowMount(App, {
// Create a shallow instance of the component
data: {
messages: ["Cat"]
}
});
});
it('equals messages to ["Cat"]', () => {
// Within cmp.vm, we can access all Vue instance methods
expect(cmp.vm.messages).toEqual(["Cat"]);
});
it("has the expected html structure", () => {
expect(cmp.element).toMatchSnapshot();
});
});

When running Jest in watch mode, you’ll see the test passes, but the Snapshot doesn’t match.

Press u to regenerate the snapshot.

Open and inspect it again:

C++
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `
<div
id="app"
>
<!-- -->
</div>
`;

Notice that no children components were generated. This means that we successfully tested the App component fully isolated from the component tree. We can now test our app without fear of calling or altering any of our other components.

To accomplish a similar test with shallow rendering, you can implement the MessageList.test.js test as follows.

C++
import { mount } from "@vue/test-utils";
import MessageList from "../src/components/MessageList";
describe("MessageList.test.js", () => {
let cmp;
beforeEach(() => {
cmp = mount(MessageList, {
// Be aware that props is overridden using `propsData`
propsData: {
messages: ["Cat"]
}
});
});
it('has received ["Cat"] as the message property', () => {
expect(cmp.vm.messages).toEqual(["Cat"]);
});
it("has the expected html structure", () => {
expect(cmp.element).toMatchSnapshot();
});
});

Setting up your Vue 3 + Vite project with Vitest & @vue/test-utils#

Let’s fast-forward to 2025 and assume you’re using Vue 3 with Vite.
The recommended test setup now includes Vitest (a lightweight test runner that aligns with Vite) and @vue/test-utils for component mounting.

npm create vue@latest # scaffold with Vite
npm install -D vitest @vue/test-utils jsdom

In your vite.config.ts (or vite.config.js), add:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
},
})

Now you can write .spec.js or .spec.ts files and run npx vitest (or npm run test) to execute them.
This setup mirrors how Vue apps are usually built now — without Jest or Vue CLI overhead.

Behavior-first testing with Vue Testing Library#

Instead of asserting internal implementation details of components, modern testing philosophy encourages behavior-oriented tests — testing how users interact with the UI rather than how it’s coded.

Using @testing-library/vue, your tests might look like:

import { render, fireEvent, screen } from '@testing-library/vue'
import MyButton from '@/components/MyButton.vue'
test('shows the right label and responds to click', async () => {
render(MyButton, { props: { label: 'Click me' } })
const btn = screen.getByRole('button', { name: /click me/i })
await fireEvent.click(btn)
expect(screen.getByText(/clicked/i)).toBeInTheDocument()
})

This pattern focuses on what the user sees and does — making your tests more resilient to internal refactors.

Shallow mount vs mount: When to choose which#

In Vue Test Utils, you can mount a component fully or shallowly stub its children.
Use shallowMount when child components bring heavy logic or complexity, but prefer full mount when you want richer integration tests.

A good rule:
If your test’s assertion depends on how children render or behave — use mount.
If you just want to isolate parent logic, use shallowMount to speed things up.

Testing components that use Pinia or Vue Router#

Components often rely on state (Pinia) or routing context (Vue Router).
Here’s how to test them:

Pinia#

Use createTestingPinia() from @pinia/testing to mock or stub store interactions:

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import MyComp from '@/components/MyComp.vue'
const wrapper = mount(MyComp, {
global: {
plugins: [createTestingPinia()],
},
})
const store = useMyStore() // uses testing pinia
store.someAction()
expect(store.someAction).toHaveBeenCalled()

By default, actions are stubbed; you can pass { stubActions: false } if you want them to execute.

Vue Router#

To test routing behavior (e.g. router-link, navigation), mount with a router instance:

import { createRouter, createWebHistory } from 'vue-router'
import { render, screen, fireEvent } from '@testing-library/vue'
import Navigation from '@/components/Navigation.vue'
const router = createRouter({
history: createWebHistory(),
routes: [ { path: '/', component: Home } ],
})
render(Navigation, {
global: { plugins: [router] },
})
await router.isReady()
await fireEvent.click(screen.getByText('Home'))
expect(router.currentRoute.value.path).toBe('/')

Handling asynchronous components with flushPromises & nextTick#

When your component makes an API call on mount or has asynchronous logic, you can’t assert immediately.
Use flushPromises() from Vue Test Utils to wait for pending Promises to resolve, and nextTick() when DOM updates are needed.

import { mount, flushPromises } from '@vue/test-utils'
import AsyncComp from '@/components/AsyncComp.vue'
jest.spyOn(api, 'fetchData').mockResolvedValue({ foo: 'bar' })
const wrapper = mount(AsyncComp)
await flushPromises()
expect(wrapper.text()).toContain('bar')

Sometimes you may even need to call flushPromises() twice with mocking libraries (like MSW) for full resolution.

Migrating from Jest to Vitest (optional note)#

If an existing codebase uses Jest and you want to incrementally shift to Vitest:

  • Vitest supports many Jest APIs (describe, expect, mocks).

  • You can keep some legacy tests in Jest while writing new ones in Vitest.

  • Over time, replace Jest references and take advantage of Vitest’s faster, tighter integration with Vite.


Wrapping up and Resources#

To see other time saving tips and walkthroughs on testing with Jest, see Educative’s course Testing Vue Components with Jest.

You’ll build on what you learned here today, reviewing some expert Jest techniques and completing more hands-on walkthroughs of testing various types of components.

By the end, you’ll be a Vue and Jest expert able to build professional, comprehensive test scripts for any components used in modern JavaScript programs.

Continue Reading#


Written By:
Alex Jover Morales