00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.