1. File and Folder Naming Conventions for Tests
- Test file naming conventions:
- Use extensions like
.test.js, .spec.js, or .spec.jsx for test files. - Keep all test files inside a dedicated folder named
__tests__. Files in this folder are automatically recognized as test files by most test runners (like Jest). - Example folder structure:
src/
├── components/
│ ├── App.js
│ ├── __tests__/
│ │ └── App.test.js
2. Testing Input and Button Events
Testing onChange Event for Input Fields
test('testing input', () => {
render(<App />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'a' } });
expect(input.value).toBe('a');
});
- Explanation:
fireEvent.change is used to simulate a change event on the input field.- The assertion
expect(input.value).toBe('a') checks if the value of the input field is updated correctly.
Testing onClick Event for Buttons
test('click event test case', () => {
render(<App />);
const btn = screen.getByRole('button');
fireEvent.click(btn);
expect(screen.getByText('updated data')).toBeInTheDocument();
});
- Explanation:
fireEvent.click simulates a click on the button.- The assertion checks if the text "updated data" appears on the screen after the button click.
3. Lifecycle Hooks: beforeEach, afterEach, beforeAll, afterAll
- Why we need these hooks:
- Lifecycle hooks help in setting up and tearing down a consistent environment before and after each test case runs.
- Hooks usage example:
beforeEach(() => {
// Runs before each test case
console.log('Setup for each test');
});
afterEach(() => {
// Runs after each test case
console.log('Cleanup after each test');
});
beforeAll(() => {
// Runs once before all test cases
console.log('Setup before all tests');
});
afterAll(() => {
// Runs once after all test cases
console.log('Cleanup after all tests');
});
4. Snapshot Testing
- What is snapshot testing?
- Snapshot testing ensures that the UI of a component does not change unexpectedly. It captures a "snapshot" of the rendered component and compares it with future snapshots.
- When to use snapshot testing?
- Use it for pure presentational components or components with minimal logic.
- Avoid using it for components with dynamic content or rapidly changing UI, as snapshots may become brittle.
- Example of snapshot testing:
test('snapshot test for App component', () => {
const container = render(<App />);
expect(container).toMatchSnapshot();
});
- Updating snapshots:
- If there are intended UI changes, run the following command to update the snapshots:
jest --updateSnapshot
- Benefits of snapshot testing:
- Helps in detecting unintended UI changes during development.
- Useful in maintaining consistent UI during production.
5. What to Test and What to Avoid
What to test:
- Components:
- Ensure proper rendering of UI elements.
- Test props, states, and events.
- Functions:
- Test utility functions and pure functions separately.
- UI interactions:
- Test user interactions such as clicks, form submissions, and API calls.
- API calls:
- Use mocking (e.g.,
jest.fn() or msw) to simulate API responses and test how components handle them.
What to avoid:
- External libraries:
- Avoid testing third-party libraries, as they already come with their own tests.
- Default React functionality:
- No need to test React’s built-in hooks like
useState or useEffect.
6. Class Component Testing
- Why test class components?
- Even though functional components are more common, class components are still used in legacy codebases.
- Testing class components involves checking their lifecycle methods (
componentDidMount, componentDidUpdate, etc.) and internal state changes. - Example of testing a class component method:
class Counter extends React.Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
test('increments count on button click', () => {
const { getByText } = render(<Counter />);
const button = getByText('Increment');
fireEvent.click(button);
expect(getByText('Count: 1')).toBeInTheDocument();
});
7. Functional Component Method Testing
- Why test functional components?
- Functional components are now the standard in React development.
- Methods inside functional components cannot be directly tested, so event-driven testing is used.
- How to implement testing for functional component methods:
- Since functional components rely on hooks, you can simulate events that trigger state changes and assert the output.
- Example:
function App() {
const [data, setData] = React.useState('');
return (
<div>
<button data-testid="btn1" onClick={() => setData('hello')}>
Update Data
</button>
<h1>{data}</h1>
</div>
);
}
test('functional component method testing', () => {
render(<App />);
const btn = screen.getByTestId('btn1');
fireEvent.click(btn);
expect(screen.getByText('hello')).toBeInTheDocument();
});
- Alternative approach:
- Extract reusable functions outside the component, test them separately, and import them into your component.
8. Important Points for Testing
- Focus on testing core functionality:
- Core UI rendering and interactions.
- Component state and props.
- API integration (mocked).
- Use mocks and spies:
- Mock API calls using libraries like
jest.fn() or axios-mock-adapter. - Use
jest.spyOn to spy on functions and ensure they are called correctly. - Use
act for asynchronous updates: - When testing async code or updates caused by promises, wrap them in
act to ensure proper handling of state updates.