Testing Library Best Practices Migration Guide
Overview
This guide addresses the systematic migration needed to fix Testing Library rule violations across the Frontend and Smoker applications. We currently have 240+ violations that need to be addressed.
Issue Categories
1. Multiple Assertions in waitFor (87 violations)
Problem: Multiple assertions within a single waitFor callback
// ❌ Bad
await waitFor(() => {
expect(element1).toBeInTheDocument();
expect(element2).toHaveTextContent('test');
expect(element3).toBeVisible();
});
Solution: Split into separate waitFor calls or use separate assertions
// ✅ Good
await waitFor(() => expect(element1).toBeInTheDocument());
await waitFor(() => expect(element2).toHaveTextContent('test'));
await waitFor(() => expect(element3).toBeVisible());
// ✅ Or if they're related to the same condition
await waitFor(() => expect(element1).toBeInTheDocument());
expect(element2).toHaveTextContent('test');
expect(element3).toBeVisible();
2. Unnecessary act Wrapping (65 violations)
Problem: Wrapping Testing Library utilities in act unnecessarily
// ❌ Bad
await act(async () => {
fireEvent.click(button);
});
Solution: Remove act wrapper for Testing Library utilities
// ✅ Good
fireEvent.click(button);
// or for user events
await user.click(button);
3. Direct Node Access (45 violations)
Problem: Accessing DOM nodes directly instead of using Testing Library queries
// ❌ Bad
const element = container.firstChild;
const buttons = container.querySelectorAll('button');
Solution: Use Testing Library queries
// ✅ Good
const element = screen.getByRole('button');
const buttons = screen.getAllByRole('button');
4. Side Effects in waitFor (35 violations)
Problem: Performing side effects inside waitFor callbacks
// ❌ Bad
await waitFor(() => {
fireEvent.click(button);
expect(result).toBe(true);
});
Solution: Move side effects outside waitFor
// ✅ Good
fireEvent.click(button);
await waitFor(() => expect(result).toBe(true));
5. Using Container Methods (8 violations)
Problem: Using container methods instead of screen queries
// ❌ Bad
const { container } = render(<Component />);
const element = container.querySelector('.class-name');
Solution: Use screen queries with appropriate roles/labels
// ✅ Good
render(<Component />);
const element = screen.getByRole('button', { name: /submit/i });
// or
const element = screen.getByTestId('submit-button');
Migration Strategy
Phase 1: Quick Wins (Low Risk)
- Remove unnecessary
actwrappers - Fix unused variable warnings
- Remove unnecessary imports
- Fix escape character issues
Phase 2: Testing Library Queries (Medium Risk)
- Replace
container.querySelectorwithscreen.getBy*queries - Replace direct node access with proper queries
- Add missing
data-testidattributes where needed
Phase 3: Async Testing Patterns (High Risk)
- Split multiple assertions in
waitFor - Move side effects out of
waitForcallbacks - Review and optimize async test patterns
Phase 4: Validation
- Run full test suite to ensure no regressions
- Verify coverage thresholds are maintained
- Update test documentation
File Priority Order
Frontend App (148 violations)
- High Priority (>20 violations):
components/settings/notifications.test.tsx(36 violations)components/history/history.test.tsx(11 violations)-
components/common/components/DynamicList.test.tsx(25 violations) -
Medium Priority (5-20 violations):
components/smoke/smoke-simple.test.tsx(7 violations)components/smoke/smoke.test.tsx(11 violations)-
components/smoke/smokeStep/smokeStep.test.tsx(19 violations) -
Low Priority (<5 violations):
App.test.tsx(fixed)components/history/smokeCards/ratingsCard.test.tsx(1 violation)- Various component files with unused imports
Smoker App (92 violations)
- High Priority:
components/home/home.test.tsx(43 violations)components/home/wifi/wifi.test.tsx(46 violations)
Tools and Commands
Lint specific files
# Check specific file
cd apps/frontend && npx eslint src/components/settings/notifications.test.tsx
# Fix auto-fixable issues
cd apps/frontend && npx eslint src/components/settings/notifications.test.tsx --fix
Run tests for specific files
# Test specific file
cd apps/frontend && npm test -- notifications.test.tsx
# Test with coverage
cd apps/frontend && npm run test:cov -- notifications.test.tsx
Best Practices Going Forward
1. Use Modern Testing Library Patterns
// Use user events for interactions
import { user } from '@testing-library/user-event';
await user.click(button);
await user.type(input, 'text');
// Use proper queries with good selectors
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText(/email address/i)
screen.getByText(/loading/i)
2. Structure Async Tests Properly
test('async behavior', async () => {
render(<Component />);
// Perform actions
fireEvent.click(screen.getByRole('button'));
// Wait for results
await waitFor(() =>
expect(screen.getByText('success')).toBeInTheDocument()
);
// Additional synchronous assertions
expect(screen.getByText('completed')).toBeInTheDocument();
});
3. Use Appropriate Queries
getByRole- Primary choice for interactive elementsgetByLabelText- Form inputsgetByText- Text contentgetByTestId- Last resort for complex scenarios
4. Avoid Common Anti-patterns
- Don't use
actwith Testing Library utilities - Don't access DOM nodes directly
- Don't put multiple assertions in
waitFor - Don't perform side effects in
waitFor
Implementation Timeline
Week 1: Quick wins (unused imports, simple fixes) Week 2-3: Testing Library queries migration Week 4-5: Async testing patterns refactor Week 6: Validation and documentation update
This systematic approach ensures we maintain test quality while improving code standards.