Let’s be real: writing Vue apps is fun, but deploying without testing? That’s like eating street food from a sketchy cart and hoping you won’t regret it later. If you’re building Vue apps for production, testing is non-negotiable. That’s where Jest and Vue Test Utils step in. Together, they form a powerhouse duo for writing fast, reliable, and maintainable tests for Vue components.
Table of contents [Show]
Why Testing Matters in Vue.js Development
Before we dive into the technical weeds, let’s pause. Why do we even test? Here are the real-world reasons:
- Catches bugs early: Saves hours of painful debugging in production.
- Ensures scalability: Large teams can safely modify components without breaking others.
- Boosts confidence: Refactors become less terrifying when tests back you up.
- Documents your code: Tests explain how your component should behave.
Setting Up the Testing Environment
Step 1: Create a Vue 3 Project
npm init vue@latest my-vue-app
cd my-vue-app
npm install
Step 2: Install Jest and Vue Test Utils
Install the testing libraries:
npm install --save-dev jest @vue/test-utils vue-jest@next babel-jestStep 3: Configure Jest
Create a jest.config.js file:
export default {
testEnvironment: "jsdom",
moduleFileExtensions: ["js", "json", "vue"],
transform: {
"^.+\\.vue$": "vue-jest",
"^.+\\js$": "babel-jest"
}
};Writing Your First Test
Step 1: Create a Simple Vue Component
Let’s build a Counter.vue component:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const count = ref(0);
function increment() {
count.value++;
}
</script>
Step 2: Write a Unit Test
Create Counter.spec.js inside a tests/unit folder:
import { mount } from "@vue/test-utils";
import Counter from "@/components/Counter.vue";
describe("Counter.vue", () => {
it("renders initial count", () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain("Count: 0");
});
it("increments count when button is clicked", async () => {
const wrapper = mount(Counter);
await wrapper.find("button").trigger("click");
expect(wrapper.text()).toContain("Count: 1");
});
});Run the tests:
npm run testTesting Props in Vue Components
Props are like gifts from parent to child. Let’s test them properly.
Example Component
<template>
<h2>Hello, {{ name }}!</h2>
</template>
<script setup>
defineProps({ name: String });
</script>
Test for Props
import { mount } from "@vue/test-utils";
import Greeting from "@/components/Greeting.vue";
describe("Greeting.vue", () => {
it("renders the passed name", () => {
const wrapper = mount(Greeting, {
props: { name: "Alice" }
});
expect(wrapper.text()).toBe("Hello, Alice!");
});
});Testing Emitted Events
Events are Vue’s way of saying, “Hey parent, something happened!” Let’s test those signals.
Example Component
<template>
<button @click="$emit('submit', 'data')">Submit</button>
</template>
Test for Events
import { mount } from "@vue/test-utils";
import SubmitButton from "@/components/SubmitButton.vue";
describe("SubmitButton.vue", () => {
it("emits an event when clicked", async () => {
const wrapper = mount(SubmitButton);
await wrapper.trigger("click");
expect(wrapper.emitted().submit[0]).toEqual(["data"]);
});
});Mocking API Calls
In real apps, components often call APIs. Instead of hitting real servers, we mock those requests.
Example Component
<template>
<div>
<p v-if="user">User: {{ user.name }}</p>
<button @click="fetchUser">Load User</button>
</div>
</template>
<script setup>
import { ref } from "vue";
import axios from "axios";
const user = ref(null);
async function fetchUser() {
const res = await axios.get("/api/user");
user.value = res.data;
}
</script>
Test with Mock Axios
import { mount } from "@vue/test-utils";
import axios from "axios";
import UserComponent from "@/components/UserComponent.vue";
jest.mock("axios");
describe("UserComponent.vue", () => {
it("fetches user on button click", async () => {
axios.get.mockResolvedValue({ data: { name: "Bob" } });
const wrapper = mount(UserComponent);
await wrapper.find("button").trigger("click");
expect(wrapper.text()).toContain("User: Bob");
});
});Snapshot Testing
Snapshots help ensure the rendered output doesn’t unexpectedly change.
import { mount } from "@vue/test-utils";
import Counter from "@/components/Counter.vue";
describe("Counter.vue Snapshot", () => {
it("matches snapshot", () => {
const wrapper = mount(Counter);
expect(wrapper.html()).toMatchSnapshot();
});
});Full Real-World Example: Todo App with Tests
Let’s build and test a mini Todo App.
TodoApp.vue
<template>
<div>
<input v-model="newTodo" placeholder="Add todo" />
<button @click="addTodo">Add</button>
<ul>
<li v-for="(todo, i) in todos" :key="i">
{{ todo }}
<button @click="removeTodo(i)">X</button>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from "vue";
const newTodo = ref("");
const todos = ref([]);
function addTodo() {
if (newTodo.value) {
todos.value.push(newTodo.value);
newTodo.value = "";
}
}
function removeTodo(index) {
todos.value.splice(index, 1);
}
</script>
TodoApp.spec.js
import { mount } from "@vue/test-utils";
import TodoApp from "@/components/TodoApp.vue";
describe("TodoApp.vue", () => {
it("adds a new todo", async () => {
const wrapper = mount(TodoApp);
await wrapper.find("input").setValue("Learn Testing");
await wrapper.find("button").trigger("click");
expect(wrapper.text()).toContain("Learn Testing");
});
it("removes a todo", async () => {
const wrapper = mount(TodoApp);
await wrapper.find("input").setValue("Learn Vue");
await wrapper.find("button").trigger("click");
await wrapper.find("button:last-of-type").trigger("click");
expect(wrapper.text()).not.toContain("Learn Vue");
});
});Best Practices for Testing Vue Apps
- Keep tests small and focused: One test = one behavior.
- Don’t over-test implementation details: Focus on what the user sees.
- Use mocks wisely: Avoid hitting real APIs during tests.
- Automate tests in CI/CD pipelines: Never deploy without them.
Conclusion
Testing Vue apps with Jest and Vue Test Utils is like having a safety net under your high-wire act. You can refactor, scale, and ship confidently, knowing that your components behave exactly as expected. Start small—write your first test today—and soon you’ll wonder how you ever lived without them.






