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' }) })})