| name | unit-tests |
| description | Rules for writing unit tests |
Cursor Rules for Unit Tests
Test File Structure and Naming
- Create test files with
.test.js extension alongside the source files
- Test files should be co-located with the source files they test
- Use descriptive test file names that match the source file name:
ComponentName.test.js for ComponentName.jsx
- For containers, use
ContainerName.test.js for ContainerName.jsx
Code Quality
- Do NOT add comments in the code of the test
- Write self-documenting test code with clear variable names and descriptive test names
- Use descriptive variable names that explain the purpose of the data
- Do NOT use
require, use import at the top of the test file
- Always use localization strings (
localize(Localization.KEY)) instead of hardcoded strings when testing text content that uses localization in the component
- Focus on testing behavior, not implementation details
- Write tests that are maintainable and won't break with minor refactorings
- Keep tests simple and focused on a single concern
Test Organization and Structure
Import Organization
Follow this import order:
- Mock imports (from
@/mocks/*) - including mockUuid when testing components that generate IDs
- Testing library imports and external library import alphabetically (
@testing-library/*, enzyme, jest)
- Internal imports alphabetically (from
@/*) - including { Localization, localize } from '@/localization/i18n' when testing localized text
- Local imports (relative paths)
Example with all common imports:
import { mockEnv } from '@/mocks/mockEnv'
import { mockReactHookForm } from '@/mocks/mockReactHookForm'
import { mockUuid } from '@/mocks/mockUuid'
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Localization, localize } from '@/localization/i18n'
import { render } from '@/utils/rendererRTL'
import { ComponentName } from './ComponentName'
jest.mock('@/utils/env', () => mockEnv)
jest.mock('react-hook-form', () => mockReactHookForm)
jest.mock('uuid', () => mockUuid)
Test Structure
import { mockEnv } from '@/mocks/mockEnv'
import { mockReactRedux } from '@/mocks/mockReactRedux'
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import flushPromises from 'flush-promises'
import { Localization, localize } from '@/localization/i18n'
import { render } from '@/utils/rendererRTL'
jest.mock('@/utils/env', () => mockEnv)
jest.mock('react-redux', () => mockReactRedux)
beforeEach(() => {
jest.clearAllMocks()
defaultProps = {
}
})
test('renders correctly when {if case}', () => {
})
Testing Patterns
Component Testing
- Use
render from '@/utils/rendererRTL' for component unit tests
- Use
@testing-library/react for integration tests
- Test component rendering, props, state changes, and user interactions
- Avoid
toMatchSnapshot() for layout testing
- Test both success and error scenarios
- Do not use
describe block
- Use
test block instead of it
- Do not use
act
- Do not mock models, localisation and any enums
- Update snapshots in case it's needed and snapshots are valid based on new changes in the code
Container Testing
Container Structure Testing
- Test both connected and disconnected components when applicable
- Test
mapStateToProps and mapDispatchToProps separately
- Use
render from @/utils/rendererRTL for integration testing
Container-Specific Testing Guidelines
- Test Redux state mapping and action dispatching
- Test component side effects
- Test conditional rendering based on props/state
- Test error boundaries and loading states
- Test user interactions that trigger Redux actions
Utility Function Testing
- Test all exported functions
- Test edge cases and error conditions
- Mock external dependencies (APIs, file system, etc.)
- Use descriptive test names that explain the expected behavior
Action/Reducer Testing
- Test action creators return correct action objects
- Test reducers handle all action types
- Test state immutability
API Testing
- Mock API calls using
jest.mock()
- Test both success and error responses
- Test loading states
- Use
flush-promises for async operations if needed
Mocking Guidelines
Mock Structure
const mockFunction = jest.fn(() => ({
unwrap: jest.fn(() => Promise.resolve(mockData))
}))
jest.mock('@/api/someApi', () => ({
someApiFunction: mockFunction
}))
Common Mocks
- Always mock
@/utils/env with mockEnv
- Mock React Redux with
mockReactRedux for action testing
- Mock notifications with
mockNotification
- Mock React Router hooks for component testing
- You can use
mockShallowComponent and its getProps
- Mock UUIDs with
mockUuid from @/mocks/mockUuid for predictable IDs in tests
UUID Mocking
When testing components that generate IDs using uuid, mock it for predictable test results:
import { mockUuid } from '@/mocks/mockUuid'
jest.mock('uuid', () => mockUuid)
test('creates object with predictable ID', () => {
const result = createObject()
expect(result.id).toBe('1')
})
Note: mockUuid generates sequential IDs ('1', '2', '3', etc.) across all tests in a file. Account for this when writing assertions.
Container-Specific Mocks
jest.mock('@/selectors/someSelector', () => ({
someSelector: jest.fn(() => mockData)
}))
jest.mock('@/actions/someAction', () => ({
someAction: jest.fn(() => ({ type: 'SOME_ACTION' }))
}))
jest.mock('@/components/SomeComponent', () => ({
SomeComponent: jest.fn(() => <div>Mocked Component</div>)
}))
var MockTableLayout
jest.mock('./ParagraphLayout', () => mockShallowComponent('ParagraphLayout'))
jest.mock('./TableLayout', () => {
const mock = mockShallowComponent('TableLayout')
MockTableLayout = mock.TableLayout
return mock
})
Testing Best Practices
Test Naming
- Use descriptive test names that explain the scenario and expected behavior
- Follow the pattern: "renders [expected behavior] when [condition]" or "does [action] when [condition]"
- Be specific about what is being tested
- Avoid generic names like "works correctly" or "renders properly"
- Example:
test('displays error message when API call fails')
- Example:
test('disables submit button when form is invalid')
- Example:
test('calls add function when form is submitted')
- For containers:
test('dispatches action when user clicks button')
Good test names:
test('renders secondary button with icon and drawer is not visible initially')
test('submits form and creates Field instance with correct values')
test('does not call add function when cancel button is clicked')
test('closes drawer after successful form submission')
Bad test names:
test('works')
test('renders correctly')
test('button test')
test('form submission')
Assertions
- Use specific assertions:
toBe(), toEqual(), toContain()
- Use
expect.any() for type checking of Functions
- Prefer
toEqual() for complete object verification over partial matches
- For containers: test Redux state changes and action dispatches
Mock Function Verification
- NEVER access mock internals like
mock.calls[0][0] or mock.lastCall[0]
- Use Jest's assertion methods for verifying mock calls
- Use
toHaveBeenNthCalledWith(n, ...args) to verify exact arguments passed to mocks
- Mock UUIDs for predictable IDs in tests using
mockUuid from @/mocks/mockUuid
Example - INCORRECT (accessing mock internals):
const fieldInstance = defaultProps.add.mock.calls[0][0]
expect(fieldInstance.name).toBe('Test Field')
expect(fieldInstance.id).toBeDefined()
Example - CORRECT (using toHaveBeenNthCalledWith with mocked UUID):
import { mockUuid } from '@/mocks/mockUuid'
jest.mock('uuid', () => mockUuid)
test('submits form with correct values', async () => {
await user.click(submitButton)
expect(defaultProps.add).toHaveBeenCalledTimes(1)
expect(defaultProps.add).toHaveBeenNthCalledWith(1, {
id: '1',
name: 'Test Field',
fieldType: 'string',
extractorId: 'test-extractor-id',
value: '',
})
})
Example - For behavior-only testing (when exact arguments don't matter):
test('calls add function when form is submitted', async () => {
await user.click(submitButton)
expect(defaultProps.add).toHaveBeenCalledTimes(1)
})
Async Testing
- Use
async/await for async operations
- Use
flush-promises when needed
- Test both resolved and rejected promises
- Use
waitFor() from React Testing Library for async UI updates
- Use
screen from '@testing-library/react'
User Interaction Testing
- Use
userEvent from @testing-library/user-event
- Test user interactions like clicks, typing, form submissions
- Verify UI updates after interactions
- For containers: verify that interactions trigger correct Redux actions
Localization Testing
- Always use
localize(Localization.KEY) instead of hardcoded strings when testing text that uses localization in the component
- Import
{ Localization, localize } from @/localization/i18n in test files
- This ensures tests remain valid if translations change
- Do NOT mock localization - use actual localization functions
Example - INCORRECT:
test('renders button with label', () => {
render(<MyButton />)
expect(screen.getByRole('button')).toHaveTextContent('Submit')
})
Example - CORRECT:
import { Localization, localize } from '@/localization/i18n'
test('renders button with label', () => {
render(<MyButton />)
expect(screen.getByRole('button')).toHaveTextContent(localize(Localization.SUBMIT))
})
Example - Finding elements with localized text:
const submitButton = buttons.find((btn) => btn.textContent === localize(Localization.SUBMIT))
expect(element).toHaveTextContent(localize(Localization.ADD_FIELD))
Error Testing
- Test error boundaries
- Test API error responses
- Test validation errors
- Test edge cases and boundary conditions
Container Error Testing
- Test error states in Redux selectors
- Test error handling in async actions
- Test error boundaries for container components
- Test loading states and error recovery
ESLint Rules for Tests
Tests automatically inherit these rules from the project configuration:
testing-library/await-async-query: "error"
testing-library/no-await-sync-query: "error"
testing-library/no-debugging-utils: "warn"
testing-library/no-node-access: "warn"
react/prop-types: "off" (in test files)
Running Tests
Yarn Commands
yarn test:manual - Run tests in watch mode
yarn test:auto - Run all tests with coverage
yarn test:ci - Run tests in CI environment
yarn test:debug - Run tests with debugger support
Running Particular Test Files
To run a specific test file, use the following command:
cd /Users/Dzmitry_Astraukh/Documents/Repos/deps-frontend && node --trace-warnings --unhandled-rejections=strict ./node_modules/jest/bin/jest.js --colors --expand --errorOnDeprecated --config ./config/jest.config.js {fileName}
Replace {fileName} with the test filename or file path. You can use either:
- Just the filename:
ComponentName.test.js
- Full path:
src/containers/ComponentName/ComponentName.test.js
Examples:
cd /Users/Dzmitry_Astraukh/Documents/Repos/deps-frontend && node --trace-warnings --unhandled-rejections=strict ./node_modules/jest/bin/jest.js --colors --expand --errorOnDeprecated --config ./config/jest.config.js KeyValuePairInsightsComparison.test.js
cd /Users/Dzmitry_Astraukh/Documents/Repos/deps-frontend && node --trace-warnings --unhandled-rejections=strict ./node_modules/jest/bin/jest.js --colors --expand --errorOnDeprecated --config ./config/jest.config.js src/containers/PromptCalibrationStudio/AddFieldDrawer/AddFieldDrawer.test.js
Note: The command includes cd to the project directory to ensure tests run with correct context. When running from terminal, use required_permissions: ["all"] to avoid sandbox restrictions.
Common Test Utilities
Mock Data
- Use factory functions for creating mock objects
- Keep mock data realistic and consistent
- Use enums for status values and constants
- Use model to create mock data in the unit tests
Container Mock Data
const mockDocument = new Document({
id: 'mock-id',
name: 'Mock Document',
status: DocumentStatus.PROCESSING
})
const mockSelectorData = {
documents: [mockDocument],
loading: false,
error: null
}
Test Setup
- Use
beforeEach for common setup
- Clear mocks between tests with
jest.clearAllMocks() if needed