🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
Testing Library Samples
Comprehensive Testing Library examples for React and Vue, covering component testing, user interactions, mocking, and advanced testing patterns
💻 React Testing Library Basics javascript
🟢 simple
⭐⭐
Fundamental React component testing with user-centric queries, assertions, and interaction testing
⏱️ 20 min
🏷️ testing, react, component testing
Prerequisites:
React, JavaScript, Jest, DOM basics
// React Testing Library Basics
// File: Button.test.js
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import Button from '../Button'
// 1. Basic component rendering
describe('Button Component', () => {
test('renders button with text', () => {
render(<Button>Click me</Button>)
// Using user-centric queries
const button = screen.getByRole('button', { name: /click me/i })
expect(button).toBeInTheDocument()
expect(button).toHaveTextContent('Click me')
})
// 2. Testing accessibility attributes
test('applies correct accessibility props', () => {
render(<Button disabled aria-label="Submit form">Submit</Button>)
const button = screen.getByRole('button', { name: /submit form/i })
expect(button).toBeDisabled()
expect(button).toHaveAttribute('aria-label', 'Submit form')
})
// 3. Testing CSS classes and styling
test('applies custom CSS classes', () => {
const { container } = render(<Button className="custom-btn primary">Styled Button</Button>)
const button = screen.getByRole('button', { name: /styled button/i })
expect(button).toHaveClass('custom-btn', 'primary')
expect(button).toHaveStyle({ backgroundColor: '#0070f3' })
})
// 4. Testing conditional rendering
test('renders different variants correctly', () => {
const { rerender } = render(<Button variant="primary">Primary</Button>)
let button = screen.getByRole('button', { name: /primary/i })
expect(button).toHaveClass('btn-primary')
rerender(<Button variant="secondary">Secondary</Button>)
button = screen.getByRole('button', { name: /secondary/i })
expect(button).toHaveClass('btn-secondary')
})
// 5. Testing click events with fireEvent
test('handles click events with fireEvent', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click me</Button>)
const button = screen.getByRole('button', { name: /click me/i })
fireEvent.click(button)
expect(handleClick).toHaveBeenCalledTimes(1)
})
// 6. Testing user interactions with userEvent (preferred)
test('handles user interactions more realistically', async () => {
const user = userEvent.setup()
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click me</Button>)
const button = screen.getByRole('button', { name: /click me/i })
await user.click(button)
expect(handleClick).toHaveBeenCalledTimes(1)
})
// 7. Testing hover and focus states
test('handles hover and focus states', async () => {
const user = userEvent.setup()
render(<Button>Interactive Button</Button>)
const button = screen.getByRole('button', { name: /interactive button/i })
// Test hover
await user.hover(button)
expect(button).toHaveStyle({ backgroundColor: '#0051cc' })
// Test focus
await user.tab()
expect(button).toHaveFocus()
})
// 8. Testing loading state
test('shows loading state correctly', () => {
render(<Button loading>Loading Button</Button>)
const button = screen.getByRole('button', { name: /loading button/i })
expect(button).toBeDisabled()
expect(button.querySelector('[data-testid="spinner"]')).toBeInTheDocument()
})
// 9. Testing async behavior
test('handles async actions', async () => {
const asyncAction = jest.fn(() => Promise.resolve('success'))
const user = userEvent.setup()
render(<Button onClick={asyncAction}>Async Button</Button>)
const button = screen.getByRole('button', { name: /async button/i })
await user.click(button)
await waitFor(() => {
expect(asyncAction).toHaveBeenCalled()
})
})
// 10. Testing custom hooks integration
test('integrates with custom hooks', () => {
const TestComponent = () => {
const [count, setCount] = React.useState(0)
return (
<Button onClick={() => setCount(c => c + 1)}>
Count: {count}
</Button>
)
}
render(<TestComponent />)
const button = screen.getByRole('button')
expect(button).toHaveTextContent('Count: 0')
fireEvent.click(button)
expect(button).toHaveTextContent('Count: 1')
})
})
// File: Form.test.js
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Form from '../Form'
describe('Form Component', () => {
test('renders all form fields', () => {
render(<Form />)
expect(screen.getByLabelText(/name/i)).toBeInTheDocument()
expect(screen.getByLabelText(/email/i)).toBeInTheDocument()
expect(screen.getByLabelText(/password/i)).toBeInTheDocument()
expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument()
})
test('validates required fields', async () => {
const user = userEvent.setup()
const handleSubmit = jest.fn()
render(<Form onSubmit={handleSubmit} />)
const submitButton = screen.getByRole('button', { name: /submit/i })
await user.click(submitButton)
// Should show validation errors
await waitFor(() => {
expect(screen.getByText(/name is required/i)).toBeInTheDocument()
expect(screen.getByText(/email is required/i)).toBeInTheDocument()
})
// Form should not be submitted
expect(handleSubmit).not.toHaveBeenCalled()
})
test('submits form with valid data', async () => {
const user = userEvent.setup()
const handleSubmit = jest.fn()
render(<Form onSubmit={handleSubmit} />)
// Fill out form
await user.type(screen.getByLabelText(/name/i), 'John Doe')
await user.type(screen.getByLabelText(/email/i), '[email protected]')
await user.type(screen.getByLabelText(/password/i), 'password123')
// Submit form
await user.click(screen.getByRole('button', { name: /submit/i }))
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith({
name: 'John Doe',
email: '[email protected]',
password: 'password123'
})
})
})
test('shows submission feedback', async () => {
const user = userEvent.setup()
render(<Form />)
// Fill and submit form
await user.type(screen.getByLabelText(/name/i), 'John Doe')
await user.type(screen.getByLabelText(/email/i), '[email protected]')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: /submit/i }))
// Should show loading state
await waitFor(() => {
expect(screen.getByText(/submitting/i)).toBeInTheDocument()
})
})
})
// File: List.test.js
import { render, screen, fireEvent } from '@testing-library/react'
import List from '../List'
describe('List Component', () => {
const items = [
{ id: 1, name: 'Item 1', completed: false },
{ id: 2, name: 'Item 2', completed: true },
{ id: 3, name: 'Item 3', completed: false }
]
test('renders list items correctly', () => {
render(<List items={items} />)
items.forEach(item => {
expect(screen.getByText(item.name)).toBeInTheDocument()
})
})
test('applies correct styling to completed items', () => {
render(<List items={items} />)
const completedItem = screen.getByText('Item 2')
expect(completedItem).toHaveClass('completed')
})
test('filters items correctly', () => {
const { rerender } = render(<List items={items} filter="completed" />)
expect(screen.queryByText('Item 1')).not.toBeInTheDocument()
expect(screen.getByText('Item 2')).toBeInTheDocument()
expect(screen.queryByText('Item 3')).not.toBeInTheDocument()
rerender(<List items={items} filter="active" />)
expect(screen.getByText('Item 1')).toBeInTheDocument()
expect(screen.queryByText('Item 2')).not.toBeInTheDocument()
expect(screen.getByText('Item 3')).toBeInTheDocument()
})
test('handles item interactions', async () => {
const onToggleComplete = jest.fn()
const onDeleteItem = jest.fn()
const user = userEvent.setup()
render(
<List
items={items}
onToggleComplete={onToggleComplete}
onDeleteItem={onDeleteItem}
/>
)
// Toggle completion
const firstItemCheckbox = screen.getAllByRole('checkbox')[0]
await user.click(firstItemCheckbox)
expect(onToggleComplete).toHaveBeenCalledWith(1)
// Delete item
const deleteButton = screen.getAllByRole('button', { name: /delete/i })[0]
await user.click(deleteButton)
expect(onDeleteItem).toHaveBeenCalledWith(1)
})
})
// File: Modal.test.js
import { render, screen, fireEvent } from '@testing-library/react'
import Modal from '../Modal'
describe('Modal Component', () => {
test('renders modal when open', () => {
render(
<Modal isOpen={true} onClose={jest.fn()}>
<h1>Modal Content</h1>
<p>This is a modal</p>
</Modal>
)
expect(screen.getByRole('dialog')).toBeInTheDocument()
expect(screen.getByText('Modal Content')).toBeInTheDocument()
expect(screen.getByText('This is a modal')).toBeInTheDocument()
})
test('does not render when closed', () => {
render(
<Modal isOpen={false} onClose={jest.fn()}>
<h1>Modal Content</h1>
</Modal>
)
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})
test('calls onClose when close button is clicked', async () => {
const onClose = jest.fn()
const user = userEvent.setup()
render(
<Modal isOpen={true} onClose={onClose}>
<h1>Modal Content</h1>
</Modal>
)
const closeButton = screen.getByRole('button', { name: /close/i })
await user.click(closeButton)
expect(onClose).toHaveBeenCalledTimes(1)
})
test('calls onClose when overlay is clicked', async () => {
const onClose = jest.fn()
const user = userEvent.setup()
render(
<Modal isOpen={true} onClose={onClose}>
<h1>Modal Content</h1>
</Modal>
)
const overlay = screen.getByTestId('modal-overlay')
await user.click(overlay)
expect(onClose).toHaveBeenCalledTimes(1)
})
test('focuses on first focusable element when opened', () => {
render(
<Modal isOpen={true} onClose={jest.fn()}>
<button>First Button</button>
<button>Second Button</button>
</Modal>
)
const firstButton = screen.getByRole('button', { name: /first button/i })
expect(firstButton).toHaveFocus()
})
test('traps focus within modal', async () => {
const user = userEvent.setup()
render(
<Modal isOpen={true} onClose={jest.fn()}>
<button>First Button</button>
<button>Second Button</button>
<button>Third Button</button>
</Modal>
)
const buttons = screen.getAllByRole('button')
// Tab through buttons
await user.tab()
expect(buttons[1]).toHaveFocus()
await user.tab()
expect(buttons[2]).toHaveFocus()
// Should cycle back to first element
await user.tab()
expect(buttons[0]).toHaveFocus()
})
})
💻 Vue Testing Library Basics javascript
🟢 simple
⭐⭐
Essential Vue component testing with Vue Testing Library, covering Vue 3 Composition API, component interactions, and testing utilities
⏱️ 20 min
🏷️ testing, vue, component testing
Prerequisites:
Vue 3, JavaScript, Testing basics, Composition API
// Vue Testing Library Basics
// File: Button.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import Button from '../Button.vue'
describe('Button Component', () => {
it('renders button with text', () => {
const wrapper = mount(Button, {
props: {
text: 'Click me'
}
})
expect(wrapper.text()).toContain('Click me')
expect(wrapper.find('button').exists()).toBe(true)
})
it('applies correct CSS classes', () => {
const wrapper = mount(Button, {
props: {
variant: 'primary',
size: 'large'
}
})
const button = wrapper.find('button')
expect(button.classes()).toContain('btn-primary')
expect(button.classes()).toContain('btn-large')
})
it('emits click event', async () => {
const wrapper = mount(Button, {
props: {
text: 'Click me'
}
})
await wrapper.find('button').trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
expect(wrapper.emitted('click')).toHaveLength(1)
})
it('is disabled when disabled prop is true', () => {
const wrapper = mount(Button, {
props: {
text: 'Disabled Button',
disabled: true
}
})
const button = wrapper.find('button')
expect(button.attributes('disabled')).toBeDefined()
expect(button.element.disabled).toBe(true)
})
it('shows loading state', () => {
const wrapper = mount(Button, {
props: {
text: 'Loading',
loading: true
}
})
const button = wrapper.find('button')
expect(button.find('[data-testid="spinner"]').exists()).toBe(true)
expect(button.attributes('disabled')).toBeDefined()
})
it('renders slot content when provided', () => {
const wrapper = mount(Button, {
slots: {
default: '<span class="custom-content">Custom Content</span>'
}
})
expect(wrapper.find('.custom-content').exists()).toBe(true)
expect(wrapper.text()).toContain('Custom Content')
})
it('handles keyboard events', async () => {
const wrapper = mount(Button, {
props: {
text: 'Keyboard Button'
}
})
await wrapper.find('button').trigger('keydown.enter')
expect(wrapper.emitted()).toHaveProperty('click')
})
})
// File: TodoList.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, beforeEach } from 'vitest'
import { nextTick } from 'vue'
import TodoList from '../TodoList.vue'
describe('TodoList Component', () => {
let wrapper
beforeEach(() => {
wrapper = mount(TodoList, {
props: {
todos: [
{ id: 1, text: 'Learn Vue', completed: false },
{ id: 2, text: 'Build App', completed: true },
{ id: 3, text: 'Deploy App', completed: false }
]
}
})
})
it('renders all todos', () => {
const todoItems = wrapper.findAll('[data-testid="todo-item"]')
expect(todoItems).toHaveLength(3)
expect(wrapper.text()).toContain('Learn Vue')
expect(wrapper.text()).toContain('Build App')
expect(wrapper.text()).toContain('Deploy App')
})
it('marks todo as completed', async () => {
const firstTodo = wrapper.find('[data-testid="todo-item"]')
const checkbox = firstTodo.find('input[type="checkbox"]')
await checkbox.setChecked(true)
await nextTick()
expect(firstTodo.classes()).toContain('completed')
expect(wrapper.emitted()).toHaveProperty('toggle-completed')
})
it('filters todos correctly', async () => {
// Test active filter
await wrapper.setData({ filter: 'active' })
await nextTick()
const activeTodos = wrapper.findAll('[data-testid="todo-item"]:not(.completed)')
expect(activeTodos).toHaveLength(2)
// Test completed filter
await wrapper.setData({ filter: 'completed' })
await nextTick()
const completedTodos = wrapper.findAll('[data-testid="todo-item"].completed')
expect(completedTodos).toHaveLength(1)
})
it('adds new todo', async () => {
const newTodoInput = wrapper.find('[data-testid="new-todo-input"]')
const addButton = wrapper.find('[data-testid="add-todo-button"]')
await newTodoInput.setValue('New Todo Item')
await addButton.trigger('click')
await nextTick()
expect(wrapper.emitted()).toHaveProperty('add-todo')
expect(wrapper.emitted('add-todo')[0]).toEqual([{ text: 'New Todo Item' }])
})
it('deletes todo', async () => {
const firstTodo = wrapper.find('[data-testid="todo-item"]')
const deleteButton = firstTodo.find('[data-testid="delete-button"]')
await deleteButton.trigger('click')
await nextTick()
expect(wrapper.emitted()).toHaveProperty('delete-todo')
expect(wrapper.emitted('delete-todo')[0]).toEqual([1])
})
})
// File: Form.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import { nextTick } from 'vue'
import ContactForm from '../ContactForm.vue'
describe('ContactForm Component', () => {
it('renders form fields correctly', () => {
const wrapper = mount(ContactForm)
expect(wrapper.find('[name="name"]').exists()).toBe(true)
expect(wrapper.find('[name="email"]').exists()).toBe(true)
expect(wrapper.find('[name="message"]').exists()).toBe(true)
expect(wrapper.find('button[type="submit"]').exists()).toBe(true)
})
it('validates required fields', async () => {
const wrapper = mount(ContactForm)
const submitButton = wrapper.find('button[type="submit"]')
await submitButton.trigger('click')
await nextTick()
expect(wrapper.text()).toContain('Name is required')
expect(wrapper.text()).toContain('Email is required')
expect(wrapper.text()).toContain('Message is required')
})
it('validates email format', async () => {
const wrapper = mount(ContactForm)
await wrapper.find('[name="email"]').setValue('invalid-email')
await wrapper.find('button[type="submit"]').trigger('click')
await nextTick()
expect(wrapper.text()).toContain('Please enter a valid email')
})
it('submits form with valid data', async () => {
const wrapper = mount(ContactForm)
await wrapper.find('[name="name"]').setValue('John Doe')
await wrapper.find('[name="email"]').setValue('[email protected]')
await wrapper.find('[name="message"]').setValue('This is a test message')
await wrapper.find('button[type="submit"]').trigger('click')
await nextTick()
expect(wrapper.emitted()).toHaveProperty('submit')
expect(wrapper.emitted('submit')[0]).toEqual({
name: 'John Doe',
email: '[email protected]',
message: 'This is a test message'
})
})
it('shows loading state during submission', async () => {
const wrapper = mount(ContactForm)
// Mock async submission
const submitSpy = vi.fn(() => Promise.resolve())
wrapper.vm.submit = submitSpy
await wrapper.find('[name="name"]').setValue('John Doe')
await wrapper.find('[name="email"]').setValue('[email protected]')
await wrapper.find('[name="message"]').setValue('Test message')
await wrapper.find('button[type="submit"]').trigger('click')
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeDefined()
expect(wrapper.text()).toContain('Submitting...')
})
})
// File: Counter.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, beforeEach } from 'vitest'
import Counter from '../Counter.vue'
describe('Counter Component', () => {
let wrapper
beforeEach(() => {
wrapper = mount(Counter, {
props: {
initialValue: 5
}
})
})
it('renders initial value', () => {
expect(wrapper.text()).toContain('Count: 5')
})
it('increments count when increment button is clicked', async () => {
const incrementButton = wrapper.find('[data-testid="increment"]')
await incrementButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Count: 6')
})
it('decrements count when decrement button is clicked', async () => {
const decrementButton = wrapper.find('[data-testid="decrement"]')
await decrementButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Count: 4')
})
it('resets count when reset button is clicked', async () => {
const incrementButton = wrapper.find('[data-testid="increment"]')
const resetButton = wrapper.find('[data-testid="reset"]')
await incrementButton.trigger('click')
await incrementButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Count: 7')
await resetButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Count: 5')
})
it('does not go below minimum value', async () => {
wrapper = mount(Counter, {
props: {
initialValue: 0,
min: 0
}
})
const decrementButton = wrapper.find('[data-testid="decrement"]')
await decrementButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Count: 0')
expect(wrapper.find('[data-testid="decrement"]').attributes('disabled')).toBeDefined()
})
it('does not go above maximum value', async () => {
wrapper = mount(Counter, {
props: {
initialValue: 10,
max: 10
}
})
const incrementButton = wrapper.find('[data-testid="increment"]')
await incrementButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Count: 10')
expect(wrapper.find('[data-testid="increment"]').attributes('disabled')).toBeDefined()
})
it('emits change event when count changes', async () => {
const incrementButton = wrapper.find('[data-testid="increment"]')
await incrementButton.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted()).toHaveProperty('change')
expect(wrapper.emitted('change')[0]).toEqual(6)
})
})
// File: Modal.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Modal from '../Modal.vue'
describe('Modal Component', () => {
it('renders when open prop is true', () => {
const wrapper = mount(Modal, {
props: {
open: true,
title: 'Test Modal'
},
slots: {
default: '<p>Modal content</p>'
}
})
expect(wrapper.find('[data-testid="modal"]').exists()).toBe(true)
expect(wrapper.text()).toContain('Test Modal')
expect(wrapper.text()).toContain('Modal content')
})
it('does not render when open prop is false', () => {
const wrapper = mount(Modal, {
props: {
open: false
}
})
expect(wrapper.find('[data-testid="modal"]').exists()).toBe(false)
})
it('emits close event when close button is clicked', async () => {
const wrapper = mount(Modal, {
props: {
open: true
}
})
const closeButton = wrapper.find('[data-testid="close-button"]')
await closeButton.trigger('click')
expect(wrapper.emitted()).toHaveProperty('close')
})
it('emits close event when overlay is clicked', async () => {
const wrapper = mount(Modal, {
props: {
open: true
}
})
const overlay = wrapper.find('[data-testid="modal-overlay"]')
await overlay.trigger('click')
expect(wrapper.emitted()).toHaveProperty('close')
})
it('prevents closing when preventClose is true', async () => {
const wrapper = mount(Modal, {
props: {
open: true,
preventClose: true
}
})
const overlay = wrapper.find('[data-testid="modal-overlay"]')
await overlay.trigger('click')
expect(wrapper.emitted('close')).toBeUndefined()
})
it('applies custom size class', () => {
const wrapper = mount(Modal, {
props: {
open: true,
size: 'large'
}
})
const modal = wrapper.find('[data-testid="modal"]')
expect(modal.classes()).toContain('modal-large')
})
})
💻 Advanced React Testing Patterns javascript
🔴 complex
⭐⭐⭐⭐
Complex testing scenarios including API mocking, custom hooks testing, integration testing, and performance testing
⏱️ 35 min
🏷️ testing, react, advanced, integration
Prerequisites:
React Testing Library, MSW, Jest, Async/await, Performance concepts
// Advanced React Testing Patterns
// File: APITest.test.js
import { render, screen, waitFor, act } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import UserProfile from '../UserProfile'
import '@testing-library/jest-dom'
// Mock API server setup
const server = setupServer(
rest.get('/api/users/:id', (req, res, ctx) => {
const { id } = req.params
if (id === '1') {
return res(
ctx.json({
id: 1,
name: 'John Doe',
email: '[email protected]',
avatar: 'https://example.com/avatar1.jpg',
bio: 'Software Developer'
})
)
}
if (id === '999') {
return res(
ctx.status(404),
ctx.json({ error: 'User not found' })
)
}
return res(ctx.status(500), ctx.json({ error: 'Server error' }))
}),
rest.put('/api/users/:id', (req, res, ctx) => {
return res(
ctx.json({
...req.body,
updatedAt: new Date().toISOString()
})
)
})
)
// Setup and cleanup
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('UserProfile API Integration', () => {
test('loads and displays user data', async () => {
render(<UserProfile userId="1" />)
// Should show loading state initially
expect(screen.getByText(/loading/i)).toBeInTheDocument()
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument()
expect(screen.getByText('[email protected]')).toBeInTheDocument()
expect(screen.getByText('Software Developer')).toBeInTheDocument()
})
// Should not show loading state anymore
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()
})
test('handles user not found error', async () => {
render(<UserProfile userId="999" />)
await waitFor(() => {
expect(screen.getByText(/user not found/i)).toBeInTheDocument()
})
})
test('handles server error gracefully', async () => {
render(<UserProfile userId="500" />)
await waitFor(() => {
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument()
expect(screen.getByRole('button', { name: /retry/i })).toBeInTheDocument()
})
})
test('allows retrying failed requests', async () => {
server.use(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Server error' }))
})
)
render(<UserProfile userId="1" />)
// Wait for error state
await waitFor(() => {
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument()
})
// Reset server to respond successfully
server.resetHandlers()
// Click retry button
const retryButton = screen.getByRole('button', { name: /retry/i })
await userEvent.click(retryButton)
// Should load successfully now
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument()
})
})
})
// File: CustomHook.test.js
import { renderHook, act, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import useUser from '../hooks/useUser'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(
ctx.json({
id: req.params.id,
name: 'Test User',
email: '[email protected]'
})
)
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
function createWrapper(queryClient) {
return ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
describe('useUser Hook', () => {
test('loads user data successfully', async () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
})
const { result } = renderHook(() => useUser('1'), {
wrapper: createWrapper(queryClient)
})
expect(result.current.isLoading).toBe(true)
expect(result.current.user).toBeNull()
expect(result.current.error).toBeNull()
await waitFor(() => {
expect(result.current.isLoading).toBe(false)
expect(result.current.user).toEqual({
id: '1',
name: 'Test User',
email: '[email protected]'
})
expect(result.current.error).toBeNull()
})
})
test('handles loading state', () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
})
const { result } = renderHook(() => useUser('1'), {
wrapper: createWrapper(queryClient)
})
expect(result.current.isLoading).toBe(true)
expect(result.current.user).toBeNull()
})
test('handles error state', async () => {
server.use(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(ctx.status(404), ctx.json({ error: 'Not found' }))
})
)
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
})
const { result } = renderHook(() => useUser('999'), {
wrapper: createWrapper(queryClient)
})
await waitFor(() => {
expect(result.current.isLoading).toBe(false)
expect(result.current.user).toBeNull()
expect(result.current.error).toBeTruthy()
})
})
test('refetches data', async () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
})
const { result } = renderHook(() => useUser('1'), {
wrapper: createWrapper(queryClient)
})
// Wait for initial load
await waitFor(() => {
expect(result.current.user).toBeTruthy()
})
const initialUser = result.current.user
// Mock different data for refetch
server.use(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(
ctx.json({
id: req.params.id,
name: 'Updated User',
email: '[email protected]'
})
)
})
)
// Refetch data
await act(async () => {
await result.current.refetch()
})
await waitFor(() => {
expect(result.current.user).toEqual({
id: '1',
name: 'Updated User',
email: '[email protected]'
})
})
expect(result.current.user).not.toEqual(initialUser)
})
})
// File: Integration.test.js
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import App from '../App'
import '@testing-library/jest-dom'
const server = setupServer(
rest.get('/api/posts', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, title: 'First Post', content: 'First content' },
{ id: 2, title: 'Second Post', content: 'Second content' }
])
)
}),
rest.post('/api/posts', (req, res, ctx) => {
return res(
ctx.json({
id: 3,
...req.body,
createdAt: new Date().toISOString()
})
)
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('App Integration Tests', () => {
test('renders full application flow', async () => {
render(<App />)
// Should show initial loading
expect(screen.getByText(/loading posts/i)).toBeInTheDocument()
// Should render posts after loading
await waitFor(() => {
expect(screen.getByText('First Post')).toBeInTheDocument()
expect(screen.getByText('Second Post')).toBeInTheDocument()
})
})
test('creates new post successfully', async () => {
const user = userEvent.setup()
render(<App />)
// Wait for posts to load
await waitFor(() => {
expect(screen.getByText('First Post')).toBeInTheDocument()
})
// Open new post form
const newPostButton = screen.getByRole('button', { name: /new post/i })
await user.click(newPostButton)
// Fill out form
await user.type(screen.getByLabelText(/title/i), 'New Test Post')
await user.type(screen.getByLabelText(/content/i), 'New test content')
// Submit form
await user.click(screen.getByRole('button', { name: /create post/i }))
// Should show new post in list
await waitFor(() => {
expect(screen.getByText('New Test Post')).toBeInTheDocument()
expect(screen.getByText('New test content')).toBeInTheDocument()
})
})
test('handles API errors gracefully', async () => {
server.use(
rest.get('/api/posts', (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Server error' }))
})
)
render(<App />)
await waitFor(() => {
expect(screen.getByText(/failed to load posts/i)).toBeInTheDocument()
expect(screen.getByRole('button', { name: /retry/i })).toBeInTheDocument()
})
})
test('maintains state during navigation', async () => {
const user = userEvent.setup()
render(<App />)
// Wait for initial load
await waitFor(() => {
expect(screen.getByText('First Post')).toBeInTheDocument()
})
// Navigate to a post
const firstPostLink = screen.getByRole('link', { name: /first post/i })
await user.click(firstPostLink)
// Should show post details
await waitFor(() => {
expect(screen.getByText('First content')).toBeInTheDocument()
})
// Navigate back
const backButton = screen.getByRole('button', { name: /back/i })
await user.click(backButton)
// Should show post list again
await waitFor(() => {
expect(screen.getByText('First Post')).toBeInTheDocument()
expect(screen.getByText('Second Post')).toBeInTheDocument()
})
})
})
// File: Performance.test.js
import { render, screen } from '@testing-library/react'
import { measure } from 'v8-performance'
import LargeList from '../LargeList'
describe('Performance Tests', () => {
// Generate large dataset for performance testing
const generateLargeDataset = (size) => {
return Array.from({ length: size }, (_, index) => ({
id: index + 1,
name: `Item ${index + 1}`,
description: `Description for item ${index + 1}`,
category: `Category ${(index % 10) + 1}`
}))
}
test('renders large list efficiently', () => {
const largeData = generateLargeDataset(1000)
// Measure render time
const startTime = performance.now()
render(<LargeList items={largeData} />)
const endTime = performance.now()
const renderTime = endTime - startTime
// Render should complete within reasonable time
expect(renderTime).toBeLessThan(1000) // 1 second
// Should render initial items
expect(screen.getAllByRole('listitem').length).toBeGreaterThan(0)
})
test('handles filtering efficiently', () => {
const largeData = generateLargeDataset(1000)
const { rerender } = render(<LargeList items={largeData} />)
const initialItems = screen.getAllByRole('listitem')
expect(initialItems.length).toBeLessThan(1000) // Due to virtualization
// Filter to specific category
rerender(<LargeList items={largeData} filter="Category 1" />)
const filteredItems = screen.getAllByRole('listitem')
// Should update filter quickly
const filterStartTime = performance.now()
// Trigger filter logic (this would be handled by the component)
fireEvent.change(screen.getByRole('textbox', { name: /filter/i }), {
target: { value: 'Category 1' }
})
const filterEndTime = performance.now()
const filterTime = filterEndTime - filterStartTime
expect(filterTime).toBeLessThan(100) // Filter should be very fast
})
test('memory usage remains reasonable', () => {
const largeData = generateLargeDataset(5000)
const initialMemory = performance.memory?.usedJSHeapSize || 0
const { unmount } = render(<LargeList items={largeData} />)
const afterRenderMemory = performance.memory?.usedJSHeapSize || 0
const memoryIncrease = afterRenderMemory - initialMemory
// Memory increase should be reasonable (less than 50MB)
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024)
// Cleanup and check memory release
unmount()
// Force garbage collection if available
if (global.gc) {
global.gc()
}
})
test('handles frequent updates efficiently', async () => {
const initialData = generateLargeDataset(100)
render(<LargeList items={initialData} />)
const startTime = performance.now()
// Simulate frequent updates
for (let i = 0; i < 10; i++) {
const newData = generateLargeDataset(100 + i * 10)
// This would be handled by parent component updating props
// For testing, we simulate the update time
const updateStart = performance.now()
// Trigger re-render (in real scenario, this would be props update)
fireEvent.click(screen.getByRole('button', { name: /refresh/i }))
const updateEnd = performance.now()
const updateTime = updateEnd - updateStart
// Each update should be fast
expect(updateTime).toBeLessThan(100)
}
const totalTime = performance.now() - startTime
// Total time for all updates should be reasonable
expect(totalTime).toBeLessThan(2000) // 2 seconds
})
})
💻 Advanced Vue Testing Patterns javascript
🔴 complex
⭐⭐⭐⭐
Complex Vue testing scenarios including Pinia store testing, router testing, API integration, and component communication patterns
⏱️ 35 min
🏷️ testing, vue, advanced, integration
Prerequisites:
Vue Testing Library, Pinia, Vue Router, Async/await, Component communication
// Advanced Vue Testing Patterns
// File: StoreIntegration.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import UserProfile from '../UserProfile.vue'
import { useUserStore } from '../stores/userStore'
describe('Store Integration Tests', () => {
let wrapper
let pinia
beforeEach(() => {
pinia = createPinia()
setActivePinia(pinia)
})
it('loads and displays user data from store', async () => {
// Mock store data
const userStore = useUserStore()
userStore.user = {
id: 1,
name: 'John Doe',
email: '[email protected]',
avatar: 'avatar.jpg'
}
userStore.loading = false
wrapper = mount(UserProfile, {
global: {
plugins: [pinia]
}
})
expect(wrapper.text()).toContain('John Doe')
expect(wrapper.text()).toContain('[email protected]')
})
it('shows loading state when store is loading', () => {
const userStore = useUserStore()
userStore.loading = true
userStore.user = null
wrapper = mount(UserProfile, {
global: {
plugins: [pinia]
}
})
expect(wrapper.text()).toContain('Loading...')
})
it('dispatches store actions on user actions', async () => {
const userStore = useUserStore()
userStore.user = {
id: 1,
name: 'John Doe',
email: '[email protected]'
}
userStore.loading = false
// Mock store action
const updateProfileSpy = vi.spyOn(userStore, 'updateProfile')
wrapper = mount(UserProfile, {
global: {
plugins: [pinia]
}
})
// Find and trigger edit mode
const editButton = wrapper.find('[data-testid="edit-profile"]')
await editButton.trigger('click')
await nextTick()
// Update form fields
const nameInput = wrapper.find('[data-testid="name-input"]')
await nameInput.setValue('Jane Doe')
const saveButton = wrapper.find('[data-testid="save-profile"]')
await saveButton.trigger('click')
await nextTick()
expect(updateProfileSpy).toHaveBeenCalledWith({
name: 'Jane Doe'
})
})
it('handles store errors correctly', async () => {
const userStore = useUserStore()
userStore.loading = false
userStore.error = 'Failed to load user'
wrapper = mount(UserProfile, {
global: {
plugins: [pinia]
}
})
expect(wrapper.text()).toContain('Failed to load user')
expect(wrapper.find('[data-testid="error-message"]').exists()).toBe(true)
})
})
// File: RouterIntegration.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import { createRouter, createWebHistory } from 'vue-router'
import { nextTick } from 'vue'
import App from '../App.vue'
// Mock components
const Home = { template: '<div>Home Page</div>' }
const About = { template: '<div>About Page</div>' }
const UserProfile = { template: '<div>User Profile: {{ $route.params.id }}</div>' }
describe('Router Integration Tests', () => {
let router
let wrapper
beforeEach(async () => {
router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: UserProfile, name: 'user-profile' }
]
})
wrapper = mount(App, {
global: {
plugins: [router]
}
})
await router.isReady()
})
it('renders home page by default', () => {
expect(wrapper.text()).toContain('Home Page')
})
it('navigates to about page', async () => {
const aboutLink = wrapper.find('[data-testid="about-link"]')
await aboutLink.trigger('click')
await nextTick()
await new Promise(resolve => setTimeout(resolve, 0))
expect(wrapper.text()).toContain('About Page')
})
it('navigates to user profile with dynamic route', async () => {
await router.push('/user/123')
await nextTick()
expect(wrapper.text()).toContain('User Profile: 123')
})
it('updates route params correctly', async () => {
await router.push('/user/123')
await nextTick()
expect(wrapper.text()).toContain('User Profile: 123')
await router.push('/user/456')
await nextTick()
expect(wrapper.text()).toContain('User Profile: 456')
})
it('handles navigation guards', async () => {
// Add navigation guard
router.beforeEach((to, from, next) => {
if (to.path === '/about') {
next('/login')
} else {
next()
}
})
const aboutLink = wrapper.find('[data-testid="about-link"]')
await aboutLink.trigger('click')
await nextTick()
await new Promise(resolve => setTimeout(resolve, 0))
expect(wrapper.vm.$route.path).toBe('/login')
})
it('passes route params as props', async () => {
const TestComponent = {
template: '<div>User ID: {{ userId }}</div>',
props: ['userId']
}
router.addRoute({
path: '/profile/:userId',
component: TestComponent,
props: true
})
await router.push('/profile/789')
await nextTick()
expect(wrapper.text()).toContain('User ID: 789')
})
})
// File: ComponentCommunication.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import { nextTick } from 'vue'
import ParentComponent from '../ParentComponent.vue'
import ChildComponent from '../ChildComponent.vue'
describe('Component Communication Tests', () => {
it('passes data from parent to child via props', () => {
const wrapper = mount(ParentComponent, {
props: {
message: 'Hello from parent'
}
})
const childComponent = wrapper.findComponent(ChildComponent)
expect(childComponent.props('message')).toBe('Hello from parent')
expect(wrapper.text()).toContain('Child says: Hello from parent')
})
it('emits events from child to parent', async () => {
const wrapper = mount(ParentComponent)
const childComponent = wrapper.findComponent(ChildComponent)
await childComponent.vm.$emit('child-event', 'Hello from child')
expect(wrapper.emitted('child-event')).toBeTruthy()
expect(wrapper.emitted('child-event')[0]).toEqual(['Hello from child'])
})
it('updates parent state based on child events', async () => {
const wrapper = mount(ParentComponent)
const childComponent = wrapper.findComponent(ChildComponent)
// Child component emits an update event
await childComponent.vm.$emit('update-message', 'Updated message')
await nextTick()
// Parent should reflect the updated message
expect(wrapper.vm.message).toBe('Updated message')
expect(wrapper.text()).toContain('Child says: Updated message')
})
it('communicates via provide/inject', () => {
const ParentComponent = {
template: `
<div>
<ChildComponent />
</div>
`,
provide: {
sharedData: 'Shared from parent',
sharedMethod: vi.fn()
}
}
const ChildComponent = {
template: `
<div>{{ injectedData }}</div>
`,
inject: ['sharedData', 'sharedMethod']
}
const wrapper = mount(ParentComponent, {
global: {
components: { ChildComponent }
}
})
const childComponent = wrapper.findComponent(ChildComponent)
expect(childComponent.vm.sharedData).toBe('Shared from parent')
expect(childComponent.vm.sharedMethod).toBeDefined()
})
it('communicates via event bus', async () => {
const eventBus = mitt()
const EmitterComponent = {
template: '<button @click="emitEvent">Emit Event</button>',
methods: {
emitEvent() {
eventBus.emit('test-event', 'Hello from emitter')
}
}
}
const ListenerComponent = {
template: '<div>{{ receivedMessage }}</div>',
data() {
return {
receivedMessage: ''
}
},
mounted() {
eventBus.on('test-event', (message) => {
this.receivedMessage = message
})
}
}
const wrapper = mount({
template: `
<div>
<EmitterComponent ref="emitter" />
<ListenerComponent ref="listener" />
</div>
`,
components: {
EmitterComponent,
ListenerComponent
}
})
const emitter = wrapper.findComponent({ name: 'EmitterComponent' })
await emitter.find('button').trigger('click')
await nextTick()
const listener = wrapper.findComponent({ name: 'ListenerComponent' })
expect(listener.vm.receivedMessage).toBe('Hello from emitter')
})
})
// File: AsyncComponent.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { nextTick } from 'vue'
import AsyncDataLoader from '../AsyncDataLoader.vue'
describe('Async Component Tests', () => {
let wrapper
beforeEach(() => {
vi.clearAllMocks()
})
it('shows loading state initially', () => {
wrapper = mount(AsyncDataLoader)
expect(wrapper.text()).toContain('Loading...')
})
it('loads and displays data on mount', async () => {
// Mock API call
const mockFetch = vi.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: ['item1', 'item2', 'item3'] })
})
)
global.fetch = mockFetch
wrapper = mount(AsyncDataLoader)
// Wait for async operation
await nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(wrapper.text()).toContain('item1')
expect(wrapper.text()).toContain('item2')
expect(wrapper.text()).toContain('item3')
})
it('handles API errors gracefully', async () => {
// Mock failed API call
const mockFetch = vi.fn(() =>
Promise.reject(new Error('Network error'))
)
global.fetch = mockFetch
wrapper = mount(AsyncDataLoader)
await nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
expect(wrapper.text()).toContain('Failed to load data')
})
it('can refresh data', async () => {
let callCount = 0
const mockFetch = vi.fn(() => {
callCount++
return Promise.resolve({
json: () => Promise.resolve({ data: [`item${callCount}`] })
})
})
global.fetch = mockFetch
wrapper = mount(AsyncDataLoader)
// Initial load
await nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
expect(wrapper.text()).toContain('item1')
// Trigger refresh
const refreshButton = wrapper.find('[data-testid="refresh-button"]')
await refreshButton.trigger('click')
await nextTick()
await new Promise(resolve => setTimeout(resolve, 100))
expect(callCount).toBe(2)
expect(wrapper.text()).toContain('item2')
})
it('debounces rapid requests', async () => {
const mockFetch = vi.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: ['debounced-item'] })
})
)
global.fetch = mockFetch
wrapper = mount(AsyncDataLoader)
const searchInput = wrapper.find('[data-testid="search-input"]')
// Trigger multiple rapid changes
await searchInput.setValue('search1')
await searchInput.setValue('search2')
await searchInput.setValue('search3')
// Wait for debounce timeout
await new Promise(resolve => setTimeout(resolve, 300))
// Should only call API once due to debouncing
expect(mockFetch).toHaveBeenCalledTimes(1)
})
})