Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Testowanie Composables

Kazdy krytyczny system stacji NOVA LAB musi przejsc rygorystyczne testy przed wdrozeniem. Tak samo composables - musimy miec pewnosc, ze dzialaja poprawnie w kazdym scenariuszu.

Przygotowanie srodowiska testowego

Do testowania composables uzywamy

@vue/test-utils
i Vitest (lub Jest). Kluczowa jest funkcja pomocnicza, ktora uruchamia composable wewnatrz komponentu testowego.

1// test-utils.ts
2import { createApp, defineComponent } from 'vue'
3
4// Helper: uruchom composable w izolowanym komponencie
5function withSetup(composable) {
6  let result
7
8  const TestComponent = defineComponent({
9    setup() {
10      result = composable()
11      return () => {} // pusty render
12    }
13  })
14
15  const app = createApp(TestComponent)
16  app.mount(document.createElement('div'))
17
18  return { result, app }
19}

Testowanie prostego composable

1import { describe, it, expect } from 'vitest'
2import { ref } from 'vue'
3
4// Composable do testowania
5function useCounter(initial = 0) {
6  const count = ref(initial)
7  const increment = () => count.value++
8  const decrement = () => count.value--
9  const reset = () => { count.value = initial }
10  return { count, increment, decrement, reset }
11}
12
13describe('useCounter', () => {
14  it('startuje z wartoscia poczatkowa', () => {
15    const { result } = withSetup(() => useCounter(10))
16    expect(result.count.value).toBe(10)
17  })
18
19  it('domyslnie startuje od 0', () => {
20    const { result } = withSetup(() => useCounter())
21    expect(result.count.value).toBe(0)
22  })
23
24  it('inkrementuje wartosc', () => {
25    const { result } = withSetup(() => useCounter())
26    result.increment()
27    expect(result.count.value).toBe(1)
28    result.increment()
29    expect(result.count.value).toBe(2)
30  })
31
32  it('dekrementuje wartosc', () => {
33    const { result } = withSetup(() => useCounter(5))
34    result.decrement()
35    expect(result.count.value).toBe(4)
36  })
37
38  it('resetuje do wartosci poczatkowej', () => {
39    const { result } = withSetup(() => useCounter(10))
40    result.increment()
41    result.increment()
42    result.reset()
43    expect(result.count.value).toBe(10)
44  })
45})

Mockowanie reaktywnych zaleznosci

Gdy composable zalezy od zewnetrznych zrodel danych, mockujemy je.

1import { describe, it, expect, vi } from 'vitest'
2import { ref, nextTick } from 'vue'
3
4// Composable z zaleznoscia od fetch
5function useSensorData(fetchFn) {
6  const data = ref(null)
7  const error = ref(null)
8  const loading = ref(false)
9
10  async function load(sensorId) {
11    loading.value = true
12    error.value = null
13    try {
14      data.value = await fetchFn(sensorId)
15    } catch (e) {
16      error.value = e.message
17    } finally {
18      loading.value = false
19    }
20  }
21
22  return { data, error, loading, load }
23}
24
25describe('useSensorData', () => {
26  it('laduje dane z serwera', async () => {
27    // Mock funkcji fetch
28    const mockFetch = vi.fn().mockResolvedValue({
29      id: 'T-01',
30      temperature: 22,
31      status: 'active'
32    })
33
34    const { result } = withSetup(() => useSensorData(mockFetch))
35
36    expect(result.loading.value).toBe(false)
37
38    await result.load('T-01')
39
40    expect(mockFetch).toHaveBeenCalledWith('T-01')
41    expect(result.data.value).toEqual({
42      id: 'T-01',
43      temperature: 22,
44      status: 'active'
45    })
46    expect(result.loading.value).toBe(false)
47  })
48
49  it('obsluguje bledy', async () => {
50    const mockFetch = vi.fn().mockRejectedValue(
51      new Error('Serwer niedostepny')
52    )
53
54    const { result } = withSetup(() => useSensorData(mockFetch))
55
56    await result.load('T-01')
57
58    expect(result.error.value).toBe('Serwer niedostepny')
59    expect(result.data.value).toBeNull()
60  })
61})

Testowanie composables z watch

Watchers sa asynchroniczne - potrzebujemy

nextTick
lub
flushPromises
.

1import { describe, it, expect } from 'vitest'
2import { ref, watch, nextTick, computed } from 'vue'
3
4function useSearchFilter(items) {
5  const query = ref('')
6  const filtered = computed(() => {
7    if (!query.value) return items.value
8    return items.value.filter(item =>
9      item.name.toLowerCase().includes(query.value.toLowerCase())
10    )
11  })
12
13  return { query, filtered }
14}
15
16describe('useSearchFilter', () => {
17  it('filtruje elementy po zapytaniu', async () => {
18    const items = ref([
19      { name: 'Sensor temperatury' },
20      { name: 'Sensor cisnienia' },
21      { name: 'Sensor radiacji' }
22    ])
23
24    const { result } = withSetup(() => useSearchFilter(items))
25
26    // Poczatkowo wszystkie elementy
27    expect(result.filtered.value).toHaveLength(3)
28
29    // Filtrowanie
30    result.query.value = 'temp'
31    await nextTick()
32
33    expect(result.filtered.value).toHaveLength(1)
34    expect(result.filtered.value[0].name).toBe('Sensor temperatury')
35  })
36
37  it('zwraca wszystko dla pustego zapytania', async () => {
38    const items = ref([{ name: 'A' }, { name: 'B' }])
39    const { result } = withSetup(() => useSearchFilter(items))
40
41    result.query.value = 'A'
42    await nextTick()
43    expect(result.filtered.value).toHaveLength(1)
44
45    result.query.value = ''
46    await nextTick()
47    expect(result.filtered.value).toHaveLength(2)
48  })
49})

Testowanie composables asynchronicznych

1import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2import { ref, onMounted, onUnmounted } from 'vue'
3
4function usePolling(fetchFn, interval = 5000) {
5  const data = ref(null)
6  const isPolling = ref(false)
7  let timerId = null
8
9  function startPolling() {
10    isPolling.value = true
11    timerId = setInterval(async () => {
12      data.value = await fetchFn()
13    }, interval)
14  }
15
16  function stopPolling() {
17    isPolling.value = false
18    if (timerId) {
19      clearInterval(timerId)
20      timerId = null
21    }
22  }
23
24  onUnmounted(stopPolling)
25
26  return { data, isPolling, startPolling, stopPolling }
27}
28
29describe('usePolling', () => {
30  beforeEach(() => {
31    vi.useFakeTimers()
32  })
33
34  afterEach(() => {
35    vi.restoreAllTimers()
36  })
37
38  it('odpytuje serwer w zadanych odstepach', async () => {
39    let callCount = 0
40    const mockFetch = vi.fn().mockImplementation(() => {
41      callCount++
42      return Promise.resolve({ reading: callCount * 10 })
43    })
44
45    const { result } = withSetup(() => usePolling(mockFetch, 1000))
46
47    result.startPolling()
48    expect(result.isPolling.value).toBe(true)
49
50    // Symulacja uplywu czasu
51    await vi.advanceTimersByTime(3000)
52
53    expect(mockFetch).toHaveBeenCalledTimes(3)
54
55    result.stopPolling()
56    expect(result.isPolling.value).toBe(false)
57  })
58})

Dobre praktyki testowania

  1. Izolacja - kazdy test powinien byc niezalezny
  2. Mockowanie - zewnetrzne zaleznosci zawsze mockowane
  3. Asynchronicznosc - uzywaj
    nextTick
    i
    flushPromises
    dla reactivity
  4. Cleanup - sprawdzaj czy composable poprawnie czysci zasoby
  5. Edge cases - testuj puste dane, bledy, wartosci graniczne
Ir a CodeWorlds