7.5 KiB
React Component Testing Guide
This guide explains how to write tests for React components in the MQTT-Explorer project using the generic testing utilities.
Overview
We use the following testing stack:
- Mocha - Test framework
- Chai - Assertion library
- React Testing Library - React component testing utilities
- JSDOM - DOM implementation for Node.js
- Custom Test Utilities - Located in
src/utils/spec/testUtils.tsx
Quick Start
1. Create a Test File
Test files should be placed next to the component they test with the .spec.tsx extension:
src/components/MyComponent/
├── MyComponent.tsx
└── MyComponent.spec.tsx
2. Basic Test Structure
import React from 'react'
import { expect } from 'chai'
import { describe, it } from 'mocha'
import MyComponent from './MyComponent'
import { renderWithProviders } from '../../utils/spec/testUtils'
describe('MyComponent', () => {
it('should render correctly', () => {
const { container } = renderWithProviders(<MyComponent />)
expect(container).to.exist
})
})
Using Test Utilities
renderWithProviders
This function wraps your component with necessary providers (Theme, Redux).
import { renderWithProviders } from '../../utils/spec/testUtils'
// Render with theme only (default)
const { container } = renderWithProviders(<MyComponent />, { withTheme: true })
// Render with both theme and Redux
const { container } = renderWithProviders(<MyComponent />, {
withTheme: true,
withRedux: true
})
// Render with custom theme
import { createTheme } from '@mui/material/styles'
const darkTheme = createTheme({ palette: { mode: 'dark' } })
const { container } = renderWithProviders(<MyComponent />, {
theme: darkTheme
})
// Render with custom Redux store
import { configureStore } from '@reduxjs/toolkit'
const customStore = configureStore({ /* ... */ })
const { container } = renderWithProviders(<MyComponent />, {
store: customStore,
withRedux: true
})
createMockChartData
Helper function to generate mock chart data:
import { createMockChartData } from '../../utils/spec/testUtils'
// Create 10 data points (default)
const data = createMockChartData()
// Create specific number of points
const data = createMockChartData(50)
Global Mocks
The test utilities automatically set up global mocks:
- ResizeObserver - Mocked for components using
react-resize-detector
Common Testing Patterns
Testing Rendering
it('should render without crashing', () => {
const { container } = renderWithProviders(<MyComponent />)
expect(container).to.exist
})
it('should render specific element', () => {
const { container } = renderWithProviders(<MyComponent />)
const element = container.querySelector('.my-class')
expect(element).to.exist
})
Testing Props
it('should accept all valid props', () => {
const props = {
title: 'Test',
value: 123,
onchange: () => {},
}
const { container } = renderWithProviders(<MyComponent {...props} />)
expect(container).to.exist
})
Testing User Interactions
import { userEvent } from '../../utils/spec/testUtils'
it('should handle click events', async () => {
const { container } = renderWithProviders(<MyComponent />)
const button = container.querySelector('button')
if (button) {
await userEvent.click(button)
// Assert expected behavior
}
})
Testing Different States
it('should render empty state', () => {
const { container } = renderWithProviders(<MyComponent data={[]} />)
// Assert empty state
})
it('should render with data', () => {
const data = createMockChartData(5)
const { container } = renderWithProviders(<MyComponent data={data} />)
// Assert data is rendered
})
Testing SVG Components
it('should render SVG elements', () => {
const { container } = renderWithProviders(<ChartComponent />)
const svg = container.querySelector('svg')
expect(svg).to.exist
const paths = container.querySelectorAll('path')
expect(paths.length).to.be.greaterThan(0)
const circles = container.querySelectorAll('circle')
expect(circles.length).to.equal(5)
})
Testing Edge Cases
describe('Edge Cases', () => {
it('should handle negative values', () => {
const data = [{ x: 1, y: -10 }, { x: 2, y: -20 }]
const { container } = renderWithProviders(<MyComponent data={data} />)
expect(container.querySelector('svg')).to.exist
})
it('should handle empty arrays', () => {
const { container } = renderWithProviders(<MyComponent data={[]} />)
expect(container).to.exist
})
it('should handle very large numbers', () => {
const data = [{ x: 1, y: 1000000 }]
const { container } = renderWithProviders(<MyComponent data={data} />)
expect(container).to.exist
})
})
Running Tests
Run all tests
yarn test
Run specific test file
npx mocha --require tsx --require source-map-support/register "src/components/MyComponent/MyComponent.spec.tsx"
Run tests in watch mode
npx mocha --require tsx --require source-map-support/register --watch "src/**/*.spec.{ts,tsx}"
Best Practices
-
Test Behavior, Not Implementation
- Focus on what the component does, not how it does it
- Test user-facing behavior and output
-
Use Descriptive Test Names
// Good it('should render error message when validation fails') // Bad it('test1') -
Group Related Tests
describe('MyComponent', () => { describe('Rendering', () => { it('should render correctly') it('should render with props') }) describe('Interactions', () => { it('should handle clicks') it('should handle keyboard input') }) }) -
Keep Tests Independent
- Each test should be able to run in isolation
- Don't rely on test execution order
- Clean up after each test if needed
-
Test Edge Cases
- Empty data
- Null/undefined values
- Very large/small numbers
- Negative values
- Single item arrays
-
Use Chai Assertions
expect(value).to.exist expect(value).to.be.true expect(value).to.equal(expected) expect(array).to.have.length(5) expect(number).to.be.greaterThan(0)
Example: Complete Test Suite
See src/components/Chart/Chart.spec.tsx for a comprehensive example that demonstrates:
- Multiple test groups (Rendering, Data Visualization, Edge Cases, etc.)
- Testing with different props and configurations
- Testing SVG elements
- Testing theme integration
- Performance testing
- Edge case coverage
Troubleshooting
"ResizeObserver is not defined"
This is automatically mocked by the test utilities. Make sure you're importing from testUtils.tsx.
"Cannot find module"
Check your import paths. Remember to use relative paths from the test file.
"Window is not defined"
Make sure jsdom-global/register is imported in testUtils.tsx.
Tests timing out
Increase the timeout in your test:
it('should complete async operation', function() {
this.timeout(5000) // 5 seconds
// test code
})
Adding New Test Utilities
To add new helper functions, update src/utils/spec/testUtils.tsx:
export function myNewHelper() {
// Helper implementation
}
Then use it in your tests:
import { myNewHelper } from '../../utils/spec/testUtils'