UNPKG

agent-rules-kit

Version:

Bootstrap of **Cursor** rules (`.mdc`) and mirror documentation (`.md`) for AI agent-guided projects.

386 lines (299 loc) 9.09 kB
--- description: Testing best practices for Next.js applications globs: <root>/app/**/*.ts,<root>/app/**/*.tsx,<root>/src/**/*.ts,<root>/src/**/*.tsx,<root>/pages/api/**/*.ts,<root>/tests/**/*.ts,<root>/tests/**/*.tsx alwaysApply: false --- # Next.js Testing Guide This guide outlines the recommended approach to testing Next.js applications in {projectPath}. ## Testing Stack The recommended testing tools for Next.js applications: - **Jest**: Core testing framework - **React Testing Library**: Component testing - **@testing-library/jest-dom**: DOM assertions - **Mock Service Worker (MSW)**: API mocking - **Cypress/Playwright**: End-to-end testing - **@next/jest**: Next.js-specific Jest configuration ## Setting Up Testing Environment Configure Jest with Next.js: ```js // jest.config.js const nextJest = require('@next/jest'); const createJestConfig = nextJest({ // Path to Next.js app - set to where the next.config.js is dir: './', }); const customJestConfig = { setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], moduleNameMapper: { // Handle module aliases (if you use them in tsconfig or jsconfig) '^@/components/(.*)$': '<rootDir>/components/$1', '^@/pages/(.*)$': '<rootDir>/pages/$1', }, testEnvironment: 'jest-environment-jsdom', }; module.exports = createJestConfig(customJestConfig); ``` Setup file: ```js // jest.setup.js import '@testing-library/jest-dom'; // Any global setup goes here ``` ## Testing Pages ### Testing Client-Side Rendered Pages ```jsx // pages/index.js import { useState } from 'react'; export default function Home() { const [count, setCount] = useState(0); return ( <div> <h1>Welcome to Next.js</h1> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } // __tests__/pages/index.test.js import { render, screen, fireEvent } from '@testing-library/react'; import Home from '@/pages/index'; describe('Home page', () => { it('renders the heading', () => { render(<Home />); expect( screen.getByRole('heading', { name: /welcome to next.js/i }) ).toBeInTheDocument(); }); it('increments counter on button click', () => { render(<Home />); const button = screen.getByRole('button', { name: /increment/i }); expect(screen.getByText(/count: 0/i)).toBeInTheDocument(); fireEvent.click(button); expect(screen.getByText(/count: 1/i)).toBeInTheDocument(); }); }); ``` ### Testing Pages with getServerSideProps ```jsx // pages/users.js export default function Users({ users }) { return ( <div> <h1>Users</h1> <ul> {users.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); } export async function getServerSideProps() { const res = await fetch('https://jsonplaceholder.typicode.com/users'); const users = await res.json(); return { props: { users } }; } // __tests__/pages/users.test.js import { render, screen } from '@testing-library/react'; import Users from '@/pages/users'; describe('Users page', () => { it('renders users from props', () => { const mockUsers = [ { id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' }, ]; render(<Users users={mockUsers} />); expect( screen.getByRole('heading', { name: /users/i }) ).toBeInTheDocument(); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('Jane Smith')).toBeInTheDocument(); }); }); ``` ### Testing Pages with getStaticProps Testing pages with getStaticProps is similar to testing with getServerSideProps, as you're testing the component with the props it would receive: ```jsx // __tests__/pages/static-page.test.js import { render, screen } from '@testing-library/react'; import StaticPage from '@/pages/static-page'; describe('Static page', () => { it('renders content from props', () => { const mockProps = { title: 'Static Page Title', content: 'This is static content', }; render(<StaticPage {...mockProps} />); expect( screen.getByRole('heading', { name: mockProps.title }) ).toBeInTheDocument(); expect(screen.getByText(mockProps.content)).toBeInTheDocument(); }); }); ``` ## Testing API Routes Create API mocks and test the handler directly: ```jsx // pages/api/hello.js export default function handler(req, res) { res.status(200).json({ message: 'Hello World' }); } // __tests__/api/hello.test.js import { createMocks } from 'node-mocks-http'; import handler from '@/pages/api/hello'; describe('/api/hello', () => { it('returns a message', async () => { const { req, res } = createMocks({ method: 'GET', }); await handler(req, res); expect(res._getStatusCode()).toBe(200); expect(JSON.parse(res._getData())).toEqual({ message: 'Hello World' }); }); }); ``` ## Testing App Router Components For the App Router, test server and client components differently: ### Testing Server Components Server components need to be tested with a special setup: ```jsx // __tests__/app/ServerComponent.test.jsx import { render } from '@testing-library/react'; import ServerComponent from '@/app/ServerComponent'; // Mock fetch for server components that make requests global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ data: 'mocked data' }), }) ); describe('ServerComponent', () => { it('renders server-fetched data', async () => { // Render the component (which would normally be server-rendered) const { container } = render(await ServerComponent()); // Check the rendered output expect(container.textContent).toContain('mocked data'); }); }); ``` ### Testing Client Components Client components in the App Router can be tested like regular React components: ```jsx // app/Counter.jsx (client component) 'use client'; import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } // __tests__/app/Counter.test.jsx import { render, screen, fireEvent } from '@testing-library/react'; import Counter from '@/app/Counter'; describe('Counter', () => { it('increments the count when clicked', () => { render(<Counter />); const button = screen.getByRole('button', { name: /increment/i }); expect(screen.getByText(/count: 0/i)).toBeInTheDocument(); fireEvent.click(button); expect(screen.getByText(/count: 1/i)).toBeInTheDocument(); }); }); ``` ## Testing Next.js Context Providers Many Next.js apps use context providers. Here's how to test components that use them: ```jsx // __tests__/contexts.test.jsx import { render, screen } from '@testing-library/react'; import { MyContext } from '@/contexts/MyContext'; import MyComponent from '@/components/MyComponent'; describe('Component with context', () => { it('renders with context value', () => { const contextValue = { theme: 'dark', toggleTheme: jest.fn() }; render( <MyContext.Provider value={contextValue}> <MyComponent /> </MyContext.Provider> ); expect(screen.getByText(/dark theme/i)).toBeInTheDocument(); }); }); ``` ## Testing with Next.js Router Test components that use the Next.js router: ```jsx // For Pages Router import { RouterContext } from 'next/dist/shared/lib/router-context'; const mockRouter = { pathname: '/', route: '/', query: {}, asPath: '/', push: jest.fn(), replace: jest.fn(), back: jest.fn(), }; // In your test render( <RouterContext.Provider value={mockRouter}> <MyComponent /> </RouterContext.Provider> ); // For App Router import { useRouter } from 'next/navigation'; // Mock the hook jest.mock('next/navigation', () => ({ useRouter: jest.fn(), })); // Setup the mock implementation for each test useRouter.mockImplementation(() => ({ push: jest.fn(), replace: jest.fn(), back: jest.fn(), })); ``` ## End-to-End Testing Use Cypress or Playwright for E2E testing: ```js // cypress/e2e/homepage.cy.js describe('Homepage', () => { it('navigates to about page when clicking the link', () => { cy.visit('/'); cy.findByText('About').click(); cy.url().should('include', '/about'); cy.findByRole('heading', { name: /about us/i }).should('be.visible'); }); }); ``` ## Testing Best Practices 1. **Test behavior, not implementation**: Focus on what the user experiences. 2. **Mock API requests**: Use MSW to intercept network requests. 3. **Test common user flows**: Prioritize testing common user journeys. 4. **Separate unit and integration tests**: Maintain a distinction between unit tests for individual components and integration tests for features. 5. **Organize tests by feature**: Structure tests to mirror your app's organization. ## Code Coverage Aim for high test coverage, especially for critical paths: ```json // package.json "scripts": { "test": "jest", "test:coverage": "jest --coverage" } ``` Coverage thresholds can be set in jest.config.js: ```js const customJestConfig = { // ...other config coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, }; ```