Passer au contenu

Tester Pinia avec Vitest

Introduction au test de stores Pinia

Pinia est la bibliothèque de gestion d’état recommandée pour Vue 3. Tester vos stores Pinia est essentiel pour garantir que la logique de gestion d’état de votre application fonctionne correctement.

Mise en place avec createTestingPinia

Pinia fournit une fonction createTestingPinia qui facilite le test des stores. Cette fonction crée une instance Pinia spécialement conçue pour les tests:

import { setActivePinia, createPinia } from 'pinia'
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useCounterStore } from '@/stores/counter'
import CounterComponent from '@/components/CounterComponent.vue'
describe('Counter Store', () => {
beforeEach(() => {
// Créer une nouvelle instance Pinia pour chaque test
setActivePinia(createPinia())
})
it('increments count', () => {
const store = useCounterStore()
expect(store.count).toBe(0)
store.increment()
expect(store.count).toBe(1)
})
it('doubles the count with doubleCount getter', () => {
const store = useCounterStore()
store.count = 2
expect(store.doubleCount).toBe(4)
})
})
describe('CounterComponent with Pinia', () => {
it('displays and updates counter from store', async () => {
// Créer une instance Pinia pour les tests avec des spies automatiques
const wrapper = mount(CounterComponent, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
stubActions: false
})
]
}
})
// Récupérer le store
const store = useCounterStore()
// Vérifier l'affichage initial
expect(wrapper.text()).toContain('Count: 0')
// Modifier le store
await store.increment()
// Vérifier que le composant est mis à jour
expect(wrapper.text()).toContain('Count: 1')
})
})

Options de createTestingPinia

La fonction createTestingPinia accepte plusieurs options utiles:

createTestingPinia({
// Utiliser vi.fn pour créer des espions automatiques sur les actions
createSpy: vi.fn,
// Définir à false pour exécuter réellement les actions (par défaut: true)
stubActions: false,
// Définir l'état initial des stores
initialState: {
counter: { count: 100 },
user: { name: 'Test User' }
}
})

Tester les actions Pinia

Vous pouvez tester les actions de deux façons différentes:

1. Avec stubActions: false (exécution réelle des actions)

import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useUserStore } from '@/stores/user'
describe('User Store Actions (Real Implementation)', () => {
beforeEach(() => {
setActivePinia(createPinia())
// Mocker l'API
vi.mock('@/api/user', () => ({
fetchUserProfile: vi.fn().mockResolvedValue({
id: 1,
name: 'John Doe',
})
}))
})
it('fetches and updates user profile', async () => {
const store = useUserStore()
// Vérifier l'état initial
expect(store.user).toBe(null)
expect(store.isLoading).toBe(false)
// Déclencher l'action
const fetchPromise = store.fetchUser(1)
// Vérifier l'état de chargement
expect(store.isLoading).toBe(true)
// Attendre la fin de l'action
await fetchPromise
// Vérifier l'état final
expect(store.isLoading).toBe(false)
expect(store.user).toEqual({
id: 1,
name: 'John Doe',
})
})
})

2. Avec stubActions: true (actions simulées)

import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { useUserStore } from '@/stores/user'
import UserProfile from '@/components/UserProfile.vue'
describe('UserProfile Component with Stubbed Actions', () => {
it('calls fetchUser action when mounted', async () => {
// Monter le composant avec Pinia simulé
const wrapper = mount(UserProfile, {
props: {
userId: 42
},
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
stubActions: true
})
]
}
})
const store = useUserStore()
// Vérifier que l'action a été appelée avec le bon ID
expect(store.fetchUser).toHaveBeenCalledTimes(1)
expect(store.fetchUser).toHaveBeenCalledWith(42)
// Simuler manuellement le résultat de l'action
store.user = { id: 42, name: 'Test User' }
// Vérifier que le composant affiche les données
expect(wrapper.text()).toContain('Test User')
})
})

Tester un store Pinia indépendamment

Vous pouvez tester un store Pinia sans monter de composant:

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useCartStore } from '@/stores/cart'
describe('Cart Store', () => {
beforeEach(() => {
// Créer un composant factice et monter avec Pinia pour configurer le store
mount({
template: 'none'
}, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
stubActions: false
})
]
}
})
})
it('adds items to cart', () => {
const store = useCartStore()
const product = {
id: 1,
name: 'Test Product',
price: 29.99
}
// Ajouter au panier
store.addToCart(product, 2)
// Vérifier l'état du panier
expect(store.items).toHaveLength(1)
expect(store.items[0]).toEqual({
product,
quantity: 2
})
expect(store.totalPrice).toBe(59.98)
})
})

Tester un plugin Pinia

Vous pouvez également tester les plugins Pinia:

import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import piniaLogger from '@/plugins/pinia-logger'
describe('Pinia Logger Plugin', () => {
let pinia
let consoleSpy
beforeEach(() => {
// Créer un Pinia avec le plugin
pinia = createPinia()
// Espionner console.log
consoleSpy = {
log: vi.spyOn(console, "log").mockImplementation(() => {}),
error: vi.spyOn(console, "error").mockImplementation(() => {})
}
// Appliquer le plugin à Pinia
pinia.use(piniaLogger())
// Monter un composant factice pour initialiser Pinia
mount({
template: 'none'
}, {
global: {
plugins: [pinia]
}
})
})
afterEach(() => {
consoleSpy.log.mockRestore()
consoleSpy.error.mockRestore()
})
it('logs state changes', () => {
// Définir un store simple pour tester le plugin
const useTestStore = defineStore('test', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
const store = useTestStore()
// Déclencher une action qui modifie l'état
store.increment()
// Vérifier que le plugin a loggé le changement
expect(consoleSpy.log).toHaveBeenCalled()
expect(consoleSpy.log.mock.calls[0][0]).toContain('test/increment')
})
})

Intégration avec Vue Router

Si votre store Pinia interagit avec Vue Router, vous pouvez mocker le routeur:

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { describe, it, expect, vi } from 'vitest'
import { useAuthStore } from '@/stores/auth'
describe('Auth Store with Router', () => {
it('redirects to login when logging out', async () => {
// Créer un mock du routeur
const mockRouter = {
push: vi.fn()
}
// Monter un composant factice avec le router mocké et Pinia
mount({
template: 'none'
}, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
stubActions: false
})
],
mocks: {
$router: mockRouter
}
}
})
const store = useAuthStore()
// Simuler un utilisateur authentifié
store.user = { id: 1, name: 'Test User' }
store.isAuthenticated = true
// Déconnecter l'utilisateur
await store.logout()
// Vérifier que l'état a été mis à jour
expect(store.user).toBe(null)
expect(store.isAuthenticated).toBe(false)
// Vérifier que la redirection a eu lieu
expect(mockRouter.push).toHaveBeenCalledWith({ name: 'login' })
})
})