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)
  })
})