feat: add MSW for integration test mocking

Install Mock Service Worker (MSW) and configure it for vitest.
Write one integration test for usePurchases hook using MSW.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Barcode Betty
2026-03-30 19:57:25 +00:00
committed by Barcode Betty
parent d7cfc00a8d
commit cf26dd3049
6 changed files with 800 additions and 462 deletions
+680 -462
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -34,6 +34,7 @@
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"jsdom": "^25.0.1",
"msw": "^2.12.14",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.56.1",
+45
View File
@@ -0,0 +1,45 @@
import { renderHook, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { usePurchases } from '../useApi'
import { http, HttpResponse } from 'msw'
import { server } from '../../test/mocks/server'
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
return function Wrapper({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
}
describe('useApi hooks', () => {
describe('usePurchases', () => {
it('fetches and returns purchases', async () => {
const { result } = renderHook(() => usePurchases(), { wrapper: createWrapper() })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toHaveLength(1)
expect(result.current.data![0]).toMatchObject({
id: 'pur_1',
storeName: 'Kroger',
total: 42.5,
})
})
it('returns an error when the endpoint fails', async () => {
server.use(
http.get('/api/v1/purchases', () => HttpResponse.error()),
)
const { result } = renderHook(() => usePurchases(), { wrapper: createWrapper() })
await waitFor(() => expect(result.current.isError).toBe(true))
})
})
})
+65
View File
@@ -0,0 +1,65 @@
import { http, HttpResponse } from 'msw'
import type { Purchase, Product, Coupon, PriceAlert } from '../../types/api.ts'
const mockPurchases: Purchase[] = [
{
id: 'pur_1',
storeId: 'store_1',
storeName: 'Kroger',
date: '2024-01-15',
total: 42.5,
items: [
{ id: 'item_1', productId: 'prod_1', name: 'Milk', quantity: 1, price: 3.99, unitPrice: 3.99 },
{ id: 'item_2', productId: 'prod_2', name: 'Bread', quantity: 2, price: 5.98, unitPrice: 2.99 },
],
},
]
const mockProducts: Product[] = [
{
id: 'prod_1',
name: 'Whole Milk',
brand: 'Kroger',
category: 'Dairy',
prices: [{ storeId: 'store_1', storeName: 'Kroger', price: 3.99, lastUpdated: '2024-01-15' }],
},
{
id: 'prod_2',
name: 'Whole Wheat Bread',
brand: 'Nature\'s Own',
category: 'Bakery',
prices: [{ storeId: 'store_1', storeName: 'Kroger', price: 2.99, lastUpdated: '2024-01-15' }],
},
]
const mockCoupons: Coupon[] = [
{
id: 'coupon_1',
productId: 'prod_1',
storeName: 'Kroger',
description: '$1 off milk',
discount: '$1.00',
expiresAt: '2024-12-31',
code: 'MILK1',
},
]
const mockAlerts: PriceAlert[] = [
{
id: 'alert_1',
productId: 'prod_1',
productName: 'Whole Milk',
targetPrice: 2.99,
currentPrice: 3.99,
triggered: false,
},
]
export const handlers = [
http.get('/api/v1/health', () => HttpResponse.json({ status: 'ok' })),
http.get('/api/v1/purchases', () => HttpResponse.json(mockPurchases)),
http.get('/api/v1/products', () => HttpResponse.json(mockProducts)),
http.get('/api/v1/products/prod_1', () => HttpResponse.json(mockProducts[0])),
http.get('/api/v1/coupons', () => HttpResponse.json(mockCoupons)),
http.get('/api/v1/price-alerts', () => HttpResponse.json(mockAlerts)),
]
+4
View File
@@ -0,0 +1,4 @@
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
+5
View File
@@ -1 +1,6 @@
import '@testing-library/jest-dom/vitest'
import { server } from './mocks/server'
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())