Introduction
In this article, I will show you how to write integration tests for a Vue 3 app that uses Pinia for state management.
I will be using this todo app by ar363 to demonstrate the process.
I do this because I find it easier to explain things when I work with a concrete example.
Installing Dependencies
First of all, we need a test framework to run our tests. We'll use Vitest for this.
npm install --save-dev vitest
Next, we'll add the Vue Test Utils. This library provides a lot of useful functions to test Vue components comfortably.
npm install --save-dev @vue/test-utils
Setup
Vitest uses Vite under the hood and uses the same configuration file. This is great because it means you don't have to duplicate your configuration between the two tools.
As we are working on a Vue 3 app, we'll need to ensure that the @vitejs/plugin-vue
plugin is installed and used within our vite.config.ts configuration.
Add a vite.config.ts or a vite.config.js file to the root of your project.
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
});
Configuring Vitest
Next, we'll configure Vitest. This is done by creating a vitest.config.ts or a vitest.config.js file.
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: "jsdom", // Use jsdom environment, which is needed for testing components that use the DOM
},
})
);
By default, vitest looks for any files that match this pattern ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']
.
This means that we can place our tests anywhere in our project as long as they match this pattern.
I like to put my tests in a separate folder, so I'll place them within a src/tests
folder.
We'll also add the following script to our package.json file.
{
"scripts": {
"test": "vitest"
}
}
Writing the first Test
When writing integration tests for Single Page Applications, I like to use the root component as the starting point.
I do this because the root component will include all other components, so testing this component will make sure that all components work together as expected.
Since this test doesn't have to know about the implementation details of any subcomponents (also known as "black box testing") it will be robust and have a good chance of catching real bugs.
We'll create a tests
folder inside our src
dir and add a HomeView.test.ts
file.
describe("HomeView", () => {
test("should be able to add and complete todos", async () => {
const wrapper = mount(HomeView, {
global: {
plugins: [createPinia()],
},
});
const todoInput = wrapper.find("[data-testid='todo-input']"); // Find the todo input
const addTodoButton = wrapper.find("[data-testid='todo-add-button']"); // Find the add todo button
// Create the two todos
await todoInput.setValue("First Todo");
await addTodoButton.trigger("click");
await todoInput.setValue("Second Todo");
await addTodoButton.trigger("click");
const todos = wrapper.findAll("[data-testid='todo-item']"); // Find all open todo items
// Check if there are two open todos
expect(todos.length).toBe(2);
// Check the first todo
await todos.at(0)?.setValue("checked");
const doneTodos = wrapper.findAll("[data-testid='todo-item-done']"); // Find all done todo items
// Check if there is one done todo
expect(doneTodos.length).toBe(1); // Check if there is one done todo
expect(wrapper.findAll("[data-testid='todo-item']").length).toBe(1); // Should still have one open todo
});
});
A few things to note here:
- We use the
mount
function from@vue/test-utils
to mount the component. - We use the
createPinia
function frompinia
to create a new pinia instance. We don't mock our store, because we want to test the real store. - We use the
data-testid
attribute to find elements within our component. This makes our tests more robust. See the rationale behind this here. - We don't try to test the implementation details of our components. We test the component like a real user would.
You can find the full code for this example here.
Top comments (0)