Testing
Setup
Zustand funziona con qualsiasi test runner. Gli esempi usano Vitest, ma la sintassi e’ identica per Jest.
npm install -D vitest @testing-library/react @testing-library/jest-dom
Testare lo Store Direttamente
Lo store Zustand e’ un semplice oggetto JavaScript. Puoi testare le azioni senza React.
import { describe, it, expect, beforeEach } from 'vitest'
import { useCounterStore } from '@/stores/counterStore'
describe('counterStore', () => {
beforeEach(() => {
// Reset dello store prima di ogni test
useCounterStore.setState({ count: 0 })
})
it('dovrebbe iniziare con count a 0', () => {
expect(useCounterStore.getState().count).toBe(0)
})
it('dovrebbe incrementare il contatore', () => {
useCounterStore.getState().increment()
expect(useCounterStore.getState().count).toBe(1)
})
it('dovrebbe decrementare il contatore', () => {
useCounterStore.setState({ count: 5 })
useCounterStore.getState().decrement()
expect(useCounterStore.getState().count).toBe(4)
})
it('dovrebbe resettare il contatore', () => {
useCounterStore.setState({ count: 42 })
useCounterStore.getState().reset()
expect(useCounterStore.getState().count).toBe(0)
})
})
Reset dello Store tra i Test
Zustand mantiene lo stato tra i test perche’ lo store e’ un singleton. E’ fondamentale resettarlo.
Pattern 1: setState Manuale
beforeEach(() => {
useCounterStore.setState({ count: 0 })
})
Pattern 2: Stato Iniziale Esportato
// store.ts
export const initialState = { count: 0, items: [] }
export const useStore = create<StoreState>()((set) => ({
...initialState,
// azioni...
}))
// store.test.ts
beforeEach(() => {
useStore.setState(initialState, true) // true = replace
})
Pattern 3: Reset Automatico Globale
// test/setup.ts
import { afterEach } from 'vitest'
import { useCounterStore } from '@/stores/counterStore'
import { useAuthStore } from '@/stores/authStore'
const stores = [useCounterStore, useAuthStore]
afterEach(() => {
stores.forEach((store) => {
store.setState(store.getInitialState(), true)
})
})
Per usare getInitialState(), definisci lo store con lo stato iniziale separato.
Testare Componenti con lo Store
Usa @testing-library/react per testare i componenti che usano lo store.
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, beforeEach } from 'vitest'
import { useCounterStore } from '@/stores/counterStore'
import Counter from '@/components/Counter'
describe('Counter component', () => {
beforeEach(() => {
useCounterStore.setState({ count: 0 })
})
it('dovrebbe mostrare il contatore', () => {
render(<Counter />)
expect(screen.getByText('Contatore: 0')).toBeInTheDocument()
})
it('dovrebbe incrementare al click', () => {
render(<Counter />)
fireEvent.click(screen.getByText('+1'))
expect(screen.getByText('Contatore: 1')).toBeInTheDocument()
})
it('dovrebbe riflettere stato pre-impostato', () => {
useCounterStore.setState({ count: 99 })
render(<Counter />)
expect(screen.getByText('Contatore: 99')).toBeInTheDocument()
})
})
Testing delle Azioni Async
Per testare azioni che fanno fetch o operazioni asincrone, mocka le API.
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useProductStore } from '@/stores/productStore'
// Mock globale di fetch
global.fetch = vi.fn()
describe('productStore async', () => {
beforeEach(() => {
useProductStore.setState({ products: [], loading: false, error: null })
vi.clearAllMocks()
})
it('dovrebbe caricare i prodotti', async () => {
const mockProducts = [
{ id: '1', name: 'Laptop', price: 999 },
{ id: '2', name: 'Mouse', price: 29 },
]
;(fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
ok: true,
json: async () => mockProducts,
})
await useProductStore.getState().fetchProducts()
const state = useProductStore.getState()
expect(state.products).toEqual(mockProducts)
expect(state.loading).toBe(false)
})
it('dovrebbe gestire errori di fetch', async () => {
;(fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
new Error('Network error')
)
await useProductStore.getState().fetchProducts()
const state = useProductStore.getState()
expect(state.error).toBe('Network error')
expect(state.loading).toBe(false)
})
it('dovrebbe impostare loading durante il fetch', async () => {
;(fetch as ReturnType<typeof vi.fn>).mockImplementationOnce(
() => new Promise((resolve) => setTimeout(resolve, 100))
)
const fetchPromise = useProductStore.getState().fetchProducts()
expect(useProductStore.getState().loading).toBe(true)
await fetchPromise
})
})
Mock dello Store per Isolamento
Se vuoi testare un componente in isolamento completo dallo store reale, puoi mockare l’intero modulo.
import { vi } from 'vitest'
vi.mock('@/stores/authStore', () => ({
useAuthStore: vi.fn((selector) =>
selector({
user: 'Mario',
token: 'fake-token',
login: vi.fn(),
logout: vi.fn(),
})
),
}))
Questo approccio e’ utile quando vuoi testare solo la logica del componente senza dipendere dallo stato reale dello store.