This a repository about learning to write Unit Tests in React.js with TypeScript. The tutorial and codebase were created by TrungQuanDev.
* nodejs >= v22.12.2
* npm >= v10.5.0
* yarn >= v1.22.19- Test File Naming:
*.test.ts/*.test.tsx- Standard convention*.spec.ts/*.spec.tsx- Alternative convention- Place alongside source files or in
__tests__folder
globalThis: global variable (ES2020)
Jest provides global functions (
describe,it,expect, etc.) - no imports needed.
-
Lifecycle hooks
beforeAll(fn, timeout)- Runs once before all tests in the fileafterAll(fn, timeout)- Runs once after all tests in the filebeforeEach(fn, timeout)- Runs before each testafterEach(fn, timeout)- Runs after each testtimeout: optional, default 5s (milliseconds)- All wait for promises/generators to resolve
-
describe(name, fn)- Groups related tests into a block
- Optional but recommended
- Can nest for test hierarchies
-
test(name, fn, timeout)/it(name, fn, timeout)- Runs a test
timeout: Optional, default 5s (milliseconds)
-
test.each(table)(name, fn, timeout)/it.each(table)(name, fn, timeout)- Run the same test with different data
table: Array of Arrays with test argumentsname: Supports printf formatting for parameter injection (%s,%d,%i,%f,%j,%o,%#)fn: Receives each row as arguments
Used to test values with "matcher" functions
- Provides assertions like
toBe(),toEqual(),toContain(), etc.
Mock functions (also called "spies") track function calls and control behavior
- Creating Mocks: e.g.
jest.fn(),jest.fn(), ... - Return Values: e.g.
mockFn.mockResolvedValueOnce(value),mockFn.mockRejectedValueOnce(value),mockFn.mockImplementation(), ... - Reset/Restore: e.g.
mockFn.mockReset(),mockFn.mockRestore(), ... - Assertions: e.g.
.toHaveBeenCalled(),.toHaveBeenCalledTimes(),.toHaveBeenCalledWith(), ...
The
jestobject is automatically in scope within every test file. It provides methods to create mocks and control Jest's behavior.
-
Mock Functions
jest.fn(): Returns a new, unused mock functionjest.spyOn(object, methodName):- Creates a mock function similar to
jest.fn()but also tracks calls toobject[methodName]. - Returns a Jest mock function.
- Creates a mock function similar to
jest.clearAllMocks()- Clear all mock history (calls, instances, results).
- Equivalent to calling
.mockClear()on every mocked function.
jest.restoreAllMocks()- Restore all mocks to original values.
- Equivalent to calling
.mockRestore()on every mocked function and.restore()on every replaced property.
jest.resetAllMocks()- Reset all mocks (clears history and implementations).
- Equivalent to calling
.mockReset()on every mocked function.
-
Module Mocking
jest.resetModules()- Reset module registry cache.
- Useful to isolate modules where local state might conflict between tests.
-
Fake Timers
jest.useFakeTimers()- Fake timers for all tests in file until restored with
jest.useRealTimers(). - This is a global operation affecting other tests in the same file.
- Fake timers for all tests in file until restored with
jest.useRealTimers()- Restore original implementations of global date, performance, time and timer APIs
jest.runOnlyPendingTimers()- Executes only macro-tasks currently pending (queued by
setTimeout()orsetInterval()). - New tasks scheduled by pending tasks won't execute.
- Executes only macro-tasks currently pending (queued by
jest.advanceTimersByTime(msToRun)- Fast-forward time by specified milliseconds.
- Executes all macro-tasks queued by
setTimeout(),setInterval(), andsetImmediate().
-
screen- Pre-bound to
document.bodywith all query methods - Access rendered elements
- Pre-bound to
-
Types of Queries
- Single:
getBy...,queryBy...,findBy... - Multiple:
getAllBy...,queryAllBy...,findAllBy...
- Single:
-
When to use
getBy...- Element expected (throws if not found)queryBy...- Element may not exist (returnsnull)findBy...- Element appears asynchronously (returnsPromise, default 1000ms timeout)
-
Priority Order (most accessible → least)
- Queries Accessible to Everyone
getByRole(best - includes accessibility)getByLabelText,getByPlaceholderText,getByText,getByDisplayValue, etc.
- Semantic Queries
getByAltText,getByTitle
- Test IDs
getByTestId
- Queries Accessible to Everyone
- Async Methods
findByqueries are a combination ofgetByqueries andwaitFor- Usage: Expect an element to appear but the DOM change might not happen immediately (e.g., API calls,
setTimeout, animations) - Can specify custom timeout:
findByText('text', {}, { timeout: 3000 })
- Usage: Expect an element to appear but the DOM change might not happen immediately (e.g., API calls,
userEvent: Simulates real browser user interactions (preferred overfireEventfor more realistic behavior).
-
Setup
userEvent.setup()- Initialize user instance (required before interactions)
-
Common Interactions
user.click(element)- Click elementuser.type(element, text, options?)- Type text into input- ...etc
Testing Library: React Testing
-
render(component)- Mounts component into virtual DOM for testing
- Returns queries bound to container and utility functions
-
act(callback):- Light wrapper around React's
actfunction - Forwards all arguments to
actif React version supports it - All it does is forward all arguments to the act function if your version of react supports act
- Light wrapper around React's
-
renderHook(callback, option?)- Convenience wrapper around
renderwith custom test component - Used for testing custom hooks in isolation
- Convenience wrapper around
Target Coverage: Aim for 80%+ coverage, but focus on testing critical paths and edge cases rather than chasing 100%.
| Metric | Name | Meaning |
|---|---|---|
% Stmts |
Statements | Percentage of executable statements covered |
% Branch |
Branches | Percentage of code branches executed (if-else, switch-case) |
% Funcs |
Functions | Percentage of functions called at least once |
% Lines |
Lines | Percentage of source code lines executed |
-
Standard structure for organizing unit tests:
- Arrange - Setup: mock data, dependencies, render components
- Act - Execute: perform the action being tested
- Assert - Verify: check expected outcomes with matchers
-
Example:
// Example: Complete AAA pattern it('submits form with user data', async () => { // Arrange const user = userEvent.setup(); const mockSubmit = jest.fn(); render(<LoginForm onSubmit={mockSubmit} />); // Act await user.type(screen.getByLabelText(/email/i), 'user@example.com'); await user.type(screen.getByLabelText(/password/i), 'password123'); await user.click(screen.getByRole('button', { name: /submit/i })); // Assert expect(mockSubmit).toHaveBeenCalledTimes(1); expect(mockSubmit).toHaveBeenCalledWith({ email: 'user@example.com', password: 'password123', }); });
- TrungQuanDev
- Khang Nguyen