🎯 empfohlene Sammlungen
Balanced sample collections from various categories for you to explore
Testing Library Beispiele
Umfassende Testing Library Beispiele für React und Vue Komponentententests, Benutzerinteraktionen, Mocking und fortgeschrittene Test-Patterns
💻 React Testing Library Grundlagen javascript
🟢 simple
⭐⭐
Grundlegende React-Komponentententests mit nutzerzentrierten Queries, Assertions und Interaktionstests
⏱️ 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 Grundlagen javascript
🟢 simple
⭐⭐
Essentielle Vue-Komponentententests mit Vue 3 Composition API, Komponenteninteraktionen und Test-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')
})
})
💻 Fortgeschrittene React Test-Patterns javascript
🔴 complex
⭐⭐⭐⭐
Komplexe Test-Szenarien inklusive API-Mocking, Custom-Hook-Tests, Integrationstests und Performance-Tests
⏱️ 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
})
})
💻 Fortgeschrittene Vue Test-Patterns javascript
🔴 complex
⭐⭐⭐⭐
Komplexe Vue-Test-Szenarien inklusive Pinia-Store-Tests, Router-Tests, API-Integration und Komponenten-Kommunikations-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)
})
})