• Fri, Mar 2026

Testing Vue.js Apps with Jest and Vue Test Utils

Testing Vue.js Apps with Jest and Vue Test Utils

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.

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-jest

Step 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 test

Testing 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.

This website uses cookies to enhance your browsing experience. By continuing to use this site, you consent to the use of cookies. Please review our Privacy Policy for more information on how we handle your data. Cookie Policy