Passer au contenu

Mocks et Spies dans Vitest

Les mocks et les spies sont des outils essentiels pour isoler le code testé et simuler des comportements spécifiques. Ils vous permettent de tester vos composants indépendamment de leurs dépendances.

Les fonctions de mock (vi.fn)

Une fonction de mock vous permet de remplacer une fonction réelle par une version simulée dont vous pouvez contrôler le comportement.

import { expect, test, vi } from 'vitest'
test('fonctions de mock de base', () => {
// Créer une fonction mock
const mockFn = vi.fn()
// Appeler la fonction mock
mockFn('arg1', 'arg2')
// Vérifier que la fonction a été appelée
expect(mockFn).toHaveBeenCalled()
// Vérifier les arguments d'appel
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2')
// Vérifier le nombre d'appels
expect(mockFn).toHaveBeenCalledTimes(1)
})

Contrôler le comportement des mocks

Vous pouvez définir la valeur de retour d’une fonction mock :

test('valeurs de retour des mocks', () => {
// Retourner une valeur spécifique
const mockWithReturn = vi.fn().mockReturnValue(42)
expect(mockWithReturn()).toBe(42)
// Retourner différentes valeurs à chaque appel
const mockWithMultipleReturns = vi.fn()
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call')
.mockReturnValue('default')
expect(mockWithMultipleReturns()).toBe('first call')
expect(mockWithMultipleReturns()).toBe('second call')
expect(mockWithMultipleReturns()).toBe('default')
// Implémenter une fonction personnalisée
const mockWithImplementation = vi.fn().mockImplementation((a, b) => a + b)
expect(mockWithImplementation(1, 2)).toBe(3)
})

Les spies (vi.spyOn)

Les spies permettent de surveiller les appels à une fonction existante sans changer son implémentation.

test('utilisation des spies', () => {
const user = {
getName: () => 'John',
getFullName: (lastName) => `John ${lastName}`
}
// Créer un spy sur la méthode getName
const getSpy = vi.spyOn(user, 'getName')
// L'appel à la méthode fonctionne normalement
expect(user.getName()).toBe('John')
// Mais on peut vérifier qu'elle a été appelée
expect(getSpy).toHaveBeenCalled()
// On peut aussi modifier le comportement d'un spy
getSpy.mockReturnValue('Jane')
expect(user.getName()).toBe('Jane')
// Restaurer l'implémentation originale
getSpy.mockRestore()
expect(user.getName()).toBe('John')
})

Mocker des modules entiers

Vitest permet de mocker des modules complets, ce qui est utile pour remplacer des dépendances externes.

users.js
import axios from 'axios'
export async function getUsers() {
const response = await axios.get('/api/users')
return response.data
}
// users.test.js
import { expect, test, vi } from 'vitest'
import { getUsers } from './users'
import axios from 'axios'
// Mocker le module axios
vi.mock('axios')
test('getUsers fetch les données correctement', async () => {
// Configurer le mock d'axios
const mockUsers = [{ id: 1, name: 'John' }]
axios.get.mockResolvedValue({ data: mockUsers })
// Appeler la fonction à tester
const users = await getUsers()
// Vérifier que axios.get a été appelé avec le bon argument
expect(axios.get).toHaveBeenCalledWith('/api/users')
// Vérifier que la fonction retourne les données attendues
expect(users).toEqual(mockUsers)
})

Implémentation personnalisée de modules mockés

Vous pouvez définir une implémentation personnalisée pour un module mocké :

// Mocker le module avec une implémentation personnalisée
vi.mock('./utils', () => {
return {
formatDate: () => '2023-01-01',
calculateTotal: vi.fn().mockReturnValue(100)
}
})
import { formatDate, calculateTotal } from './utils'
test('modules avec implémentation personnalisée', () => {
expect(formatDate()).toBe('2023-01-01')
expect(calculateTotal()).toBe(100)
})

Les timers simulés

Vitest peut simuler les fonctions de temporisation (setTimeout, setInterval) pour éviter d’attendre réellement le temps spécifié.

test('utilisation des timers simulés', () => {
// Activer les faux timers
vi.useFakeTimers()
const callback = vi.fn()
// Définir un timer
setTimeout(callback, 1000)
// Vérifier que le callback n'a pas encore été appelé
expect(callback).not.toHaveBeenCalled()
// Avancer dans le temps
vi.advanceTimersByTime(500)
expect(callback).not.toHaveBeenCalled()
// Avancer jusqu'à ce que le timer soit déclenché
vi.advanceTimersByTime(500)
expect(callback).toHaveBeenCalledTimes(1)
// Réinitialiser les timers
vi.useRealTimers()
})

Meilleures pratiques

  • Réinitialiser les mocks : Utilisez vi.resetAllMocks() dans les hooks beforeEach pour réinitialiser l’état des mocks entre les tests.
  • Restaurer les spies : Utilisez mockRestore() pour les spies afin de restaurer les implémentations originales.
  • Mocker au niveau approprié : Ne mocker que ce qui est nécessaire, en gardant les tests aussi proches que possible du comportement réel.
  • Utiliser des assertions claires : Spécifiez les attentes précises concernant les appels de fonctions (arguments, nombre d’appels, etc.).
  • Documenter les mocks : Commentez clairement ce que représentent vos mocks pour faciliter la compréhension des tests.

En maîtrisant les mocks et les spies, vous pourrez créer des tests isolés qui se concentrent sur un comportement spécifique sans dépendre de composants externes.