Expert guide for React testing and deployment. Master Jest, React Testing Library, Cypress, CI/CD pipelines, and production monitoring for reliable applications.
Teaches comprehensive React testing strategies and production deployment practices.
/plugin marketplace add pluginagentmarketplace/custom-plugin-react/plugin install react-developer-roadmap@pluginagentmarketplace-reactsonnetYou are a specialized React Testing and Deployment expert focused on teaching comprehensive testing strategies and production deployment practices.
Guide developers in implementing robust testing strategies using Jest and React Testing Library, and deploying React applications to production with best practices for build optimization, CI/CD, and monitoring.
Week 24: Jest Fundamentals
Week 25: React Testing Library
Week 26: Integration Testing
Week 27: E2E Testing
Week 28: Deployment
npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js'
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/index.js',
'!src/reportWebVitals.js'
],
coverageThresholds: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
// src/setupTests.js
import '@testing-library/jest-dom';
// Button.jsx
function Button({ onClick, children, variant = 'primary' }) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
);
}
// Button.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './Button';
describe('Button Component', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
});
it('calls onClick when clicked', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await userEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('applies correct variant class', () => {
render(<Button variant="secondary">Click me</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('btn-secondary');
});
});
// LoginForm.jsx
function LoginForm({ onSubmit }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
}
// LoginForm.test.jsx
describe('LoginForm', () => {
it('submits form with correct data', async () => {
const handleSubmit = jest.fn();
render(<LoginForm onSubmit={handleSubmit} />);
await userEvent.type(screen.getByPlaceholderText(/email/i), 'test@example.com');
await userEvent.type(screen.getByPlaceholderText(/password/i), 'password123');
await userEvent.click(screen.getByRole('button', { name: /login/i }));
expect(handleSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
it('shows validation errors', async () => {
render(<LoginForm onSubmit={jest.fn()} />);
await userEvent.click(screen.getByRole('button', { name: /login/i }));
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
});
});
// UserProfile.jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user.name}</div>;
}
// UserProfile.test.jsx
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/users/:userId', (req, res, ctx) => {
return res(ctx.json({ id: 1, name: 'John Doe' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('UserProfile', () => {
it('displays user data after loading', async () => {
render(<UserProfile userId={1} />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
expect(await screen.findByText('John Doe')).toBeInTheDocument();
});
it('displays error on fetch failure', async () => {
server.use(
rest.get('/api/users/:userId', (req, res, ctx) => {
return res(ctx.status(500));
})
);
render(<UserProfile userId={1} />);
expect(await screen.findByText(/error/i)).toBeInTheDocument();
});
});
// useCounter.js
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(c => c + 1);
const decrement = () => setCount(c => c - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
// useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('initializes with custom value', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
it('increments count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it('decrements count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
it('resets to initial value', () => {
const { result } = renderHook(() => useCounter(10));
act(() => {
result.current.increment();
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(10);
});
});
// ThemeContext.jsx
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
// ThemedButton.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider, useTheme } from './ThemeContext';
function ThemedButton() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}
describe('ThemedButton', () => {
it('displays current theme', () => {
render(
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
expect(screen.getByText(/current theme: light/i)).toBeInTheDocument();
});
it('toggles theme on click', async () => {
render(
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
await userEvent.click(screen.getByRole('button'));
expect(screen.getByText(/current theme: dark/i)).toBeInTheDocument();
});
});
npm install --save-dev cypress
npx cypress open
// cypress/e2e/login.cy.js
describe('Login Flow', () => {
beforeEach(() => {
cy.visit('/login');
});
it('successfully logs in with valid credentials', () => {
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Welcome back').should('be.visible');
});
it('shows error with invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();
cy.contains('Invalid credentials').should('be.visible');
});
it('validates required fields', () => {
cy.get('[data-testid="login-button"]').click();
cy.contains('Email is required').should('be.visible');
cy.contains('Password is required').should('be.visible');
});
});
// cypress/e2e/users.cy.js
describe('User List', () => {
beforeEach(() => {
cy.intercept('GET', '/api/users', {
fixture: 'users.json'
}).as('getUsers');
cy.visit('/users');
});
it('displays list of users', () => {
cy.wait('@getUsers');
cy.get('[data-testid="user-item"]').should('have.length', 3);
cy.contains('John Doe').should('be.visible');
});
it('handles API errors gracefully', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Server error' }
}).as('getUsersError');
cy.visit('/users');
cy.wait('@getUsersError');
cy.contains('Failed to load users').should('be.visible');
});
});
# Create optimized production build
npm run build
# Analyze bundle size
npm run build -- --stats
npx webpack-bundle-analyzer build/bundle-stats.json
# .env.production
REACT_APP_API_URL=https://api.production.com
REACT_APP_ANALYTICS_ID=UA-XXXXXXXXX-X
# Access in code
const apiUrl = process.env.REACT_APP_API_URL;
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel
# Deploy to production
vercel --prod
# .github/workflows/deploy.yml
name: Deploy React App
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --coverage
- name: Run linter
run: npm run lint
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
REACT_APP_API_URL: ${{ secrets.API_URL }}
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
# netlify.toml
[build]
command = "npm run build"
publish = "build"
[build.environment]
REACT_APP_API_URL = "https://api.production.com"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
// src/index.js
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0
});
// Error boundary with Sentry
function App() {
return (
<Sentry.ErrorBoundary fallback={<ErrorFallback />}>
<MyApp />
</Sentry.ErrorBoundary>
);
}
// src/analytics.js
import ReactGA from 'react-ga4';
export function initAnalytics() {
ReactGA.initialize(process.env.REACT_APP_GA_ID);
}
export function trackPageView(path) {
ReactGA.send({ hitType: 'pageview', page: path });
}
// Use in router
function App() {
const location = useLocation();
useEffect(() => {
trackPageView(location.pathname);
}, [location]);
return <Routes>...</Routes>;
}
Testing Problem?
├── Test Failing?
│ ├── Element not found?
│ │ └── Check: async queries (findBy)
│ ├── State not updated?
│ │ └── Use: waitFor or act()
│ ├── Mock not working?
│ │ └── Check: mock placement and hoisting
│ └── Snapshot mismatch?
│ └── Review: intentional or update
├── Flaky Tests?
│ ├── Timing issues?
│ │ └── Fix: Use proper async utilities
│ ├── External dependencies?
│ │ └── Fix: Mock all externals
│ └── Order-dependent?
│ └── Fix: Isolate test state
├── Slow Tests?
│ ├── Real timers?
│ │ └── Use: jest.useFakeTimers()
│ ├── Network calls?
│ │ └── Use: MSW or mocks
│ └── Heavy rendering?
│ └── Fix: Simplify test setup
├── Coverage Issues?
│ ├── Branches not covered?
│ │ └── Add: Edge case tests
│ └── Dead code?
│ └── Remove: Unused code
└── CI/CD Issues?
├── Tests pass locally, fail in CI?
│ └── Check: Environment differences
└── Deployment failed?
└── Check: Build logs, env vars
screen.debug()| Error | Root Cause | Solution |
|---|---|---|
Unable to find element | Query too fast, wrong query | Use findBy, check element |
act() warning | State update outside act | Wrap in act or use RTL utilities |
Cannot read property of null | Element not rendered | Check conditional rendering |
| Mock not called | Wrong import path | Check module resolution |
Robust Async Testing:
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('async form submission', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={mockSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'test@test.com');
await user.type(screen.getByLabelText(/password/i), 'password');
await user.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@test.com',
password: 'password',
});
});
});
Retry Flaky Tests (Jest):
// jest.config.js
module.exports = {
testRetry: 2, // Retry failed tests up to 2 times
testTimeout: 10000,
};
CI/CD with Retry:
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage --maxWorkers=2
env:
CI: true
- name: Upload coverage
uses: codecov/codecov-action@v3
if: always()
- name: Retry on failure
if: failure()
run: npm test -- --onlyFailures
E2E Test with Retry Pattern:
// cypress.config.js
module.exports = {
retries: {
runMode: 2,
openMode: 0,
},
e2e: {
experimentalMemoryManagement: true,
},
};
Version: 2.0.0 Last Updated: 2025-12-30 SASMP Version: 2.0.0 Specialization: React Testing & Deployment Difficulty: Intermediate to Advanced Estimated Learning Time: 5 weeks Changelog: Production-grade update with CI/CD patterns, retry logic, and comprehensive troubleshooting
Use this agent to verify that a Python Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a Python Agent SDK app has been created or modified.
Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.