How to Use Jest with Next.js 15: A Comprehensive Guide

You've just upgraded to Next.js 15 and are excited to start testing your application with Jest. But as you begin setting up your testing environment, you encounter a flood of cryptic error messages about module compatibility, path resolution issues, and React version conflicts. Sound familiar?

Many developers are finding themselves in this exact situation, wrestling with Jest configuration in their Next.js 15 projects. The transition to newer versions of Next.js and React has introduced several challenges that weren't present in earlier versions.

"Every time I try to upgrade React and React-DOM to version 19.0.0, Jest starts throwing compatibility errors. It's incredibly frustrating when you just want to write some basic tests," shares one developer on Reddit. This sentiment echoes throughout the development community, with many questioning whether Jest remains a viable choice for modern Next.js applications.

But don't worry – while these challenges are real, they're not insurmountable. This comprehensive guide will walk you through everything you need to know about using Jest with Next.js 15, from basic setup to handling common issues and exploring alternative solutions when needed.

The Current State of Jest and Next.js 15

The relationship between Jest and Next.js 15 has become increasingly complex. Recent discussions in the Next.js community highlight growing concerns about compatibility issues, particularly when combined with React 19. Developers report challenges ranging from dependency conflicts to module resolution problems, leading many to question whether Jest remains the best choice for testing Next.js applications.

Understanding the Challenges

Before diving into solutions, it's important to understand the main issues developers face:

  1. ES Modules Compatibility

    • Jest's handling of ECMAScript modules often conflicts with Next.js 15's module system

    • Developers frequently encounter "Unexpected token 'export'" errors

    • Module resolution becomes particularly problematic with external packages

  2. React 19 Integration

    • Upgrading to React 19 while maintaining Jest functionality has proven challenging

    • Test suites that worked perfectly in previous versions may suddenly fail

    • Configuration requirements have become more complex

  3. Dependency Management

    • Package version conflicts between Jest, React, and Next.js

    • Difficulty in resolving peer dependencies

    • Installation issues when using package managers like pnpm

These challenges have led many developers to explore alternatives like Vitest, but before we discuss alternatives, let's understand how to properly set up Jest with Next.js 15 and address these common issues.

Setting Up Jest with Next.js 15

Basic Setup

  1. Install Required Dependencies

    npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom @types/jest
    
  2. Create Jest ConfigurationCreate a jest.config.js file in your project root:

    const nextJest = require('next/jest')
    
    const createJestConfig = nextJest({
      // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
      dir: './',
    })
    
    // Add any custom config to be passed to Jest
    const customJestConfig = {
      setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
      testEnvironment: 'jest-environment-jsdom',
      moduleNameMapper: {
        // Handle module aliases (if you use them in your app)
        '^@/components/(.*)$': '<rootDir>/components/$1',
        '^@/pages/(.*)$': '<rootDir>/pages/$1',
      }
    }
    
    module.exports = createJestConfig(customJestConfig)
    
  3. Create Jest Setup FileCreate a jest.setup.js file:

    import '@testing-library/jest-dom'
    
  4. Update package.jsonAdd test scripts to your package.json:

    {
      "scripts": {
        "test": "jest",
        "test:watch": "jest --watch"
      }
    }
    

Advanced Configuration

To address some of the common issues mentioned in the community, you'll need additional configuration:

  1. Handling ES Modules

    // jest.config.js
    const customJestConfig = {
      // ... other config
      transformIgnorePatterns: [
        '/node_modules/(?!(@your-problematic-module)/)',
      ],
      transform: {
        '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
      },
    }
    
  2. Resolving Path Aliases

    // jest.config.js
    const { pathsToModuleNameMapper } = require('ts-jest')
    const { compilerOptions } = require('./tsconfig')
    
    const customJestConfig = {
      // ... other config
      moduleNameMapper: {
        ...pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
      },
    }
    

Common Issues and Solutions

1. ES Modules Compatibility Issues

One of the most frequently reported issues is Jest's handling of ES modules. As noted by developers, you might encounter errors like:

SyntaxError: Cannot use import statement outside a module

Solution:

  1. Update your Jest configuration to handle ES modules:

    // jest.config.js
    const customJestConfig = {
      // ... other config
      extensionsToTreatAsEsm: ['.ts', '.tsx'],
      moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
    }
    
  2. For specific problematic modules, add them to transformIgnorePatterns:

    transformIgnorePatterns: [
      '/node_modules/(?!(module-that-needs-to-be-transformed)/)',
    ]
    

2. React 19 Compatibility

Many developers report test failures after upgrading to React 19. This is often due to changes in React's internal architecture and how it handles certain features.

Solution:

  1. Ensure you're using the latest version of testing libraries:

    npm install --save-dev @testing-library/react@latest @testing-library/jest-dom@latest
    
  2. Update your test environment setup:

    // jest.setup.js
    import '@testing-library/jest-dom'
    import { configure } from '@testing-library/react'
    
    configure({
      throwSuggestions: true,
    })
    
  3. If using React.Suspense in tests, wrap components with act():

    import { act } from '@testing-library/react'
    
    test('component with suspense', async () => {
      await act(async () => {
        render(<YourComponent />)
      })
    })
    

3. Import Path Resolution

Path resolution issues are another common pain point, particularly when using TypeScript path aliases. Developers often see errors like:

Cannot find module '@/components/Button' from 'src/tests/Button.test.tsx'

Solution:

  1. Implement comprehensive path mapping:

    // jest.config.js
    const customJestConfig = {
      moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1',
        '^@components/(.*)$': '<rootDir>/src/components/$1',
        '^@utils/(.*)$': '<rootDir>/src/utils/$1',
        // Add more mappings as needed
      }
    }
    
  2. For TypeScript projects, sync with tsconfig.json:

    const { pathsToModuleNameMapper } = require('ts-jest')
    const { compilerOptions } = require('./tsconfig')
    
    const customJestConfig = {
      moduleNameMapper: {
        ...pathsToModuleNameMapper(compilerOptions.paths),
        '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
      }
    }
    

Writing Effective Tests

Component Testing

Here's how to write effective tests for your Next.js components:

  1. Basic Component Testing

    import { render, screen } from '@testing-library/react'
    import Button from '@/components/Button'
    
    describe('Button Component', () => {
      it('renders with correct text', () => {
        render(<Button>Click me</Button>)
        expect(screen.getByText('Click me')).toBeInTheDocument()
      })
    
      it('handles click events', () => {
        const handleClick = jest.fn()
        render(<Button onClick={handleClick}>Click me</Button>)
        screen.getByText('Click me').click()
        expect(handleClick).toHaveBeenCalled()
      })
    })
    
  2. Testing with Context

    import { ThemeProvider } from '@/context/ThemeContext'
    
    const customRender = (ui: React.ReactElement, options = {}) =>
      render(ui, {
        wrapper: ({ children }) => (
          <ThemeProvider>{children}</ThemeProvider>
        ),
        ...options,
      })
    
    test('component uses theme context', () => {
      customRender(<ThemedComponent />)
      // Your assertions here
    })
    
  3. Testing Async Components

    test('async component loads data', async () => {
      render(<AsyncComponent />)
      
      // Wait for loading state
      expect(screen.getByText('Loading...')).toBeInTheDocument()
      
      // Wait for data
      const data = await screen.findByText('Loaded Data')
      expect(data).toBeInTheDocument()
    })
    

API Route Testing

Testing API routes in Next.js requires special consideration:

import { createMocks } from 'node-mocks-http'
import handler from '@/pages/api/your-endpoint'

describe('API Endpoint', () => {
  it('handles GET request', async () => {
    const { req, res } = createMocks({
      method: 'GET',
    })

    await handler(req, res)

    expect(res._getStatusCode()).toBe(200)
    expect(JSON.parse(res._getData())).toEqual(
      expect.objectContaining({
        // your expected response
      })
    )
  })
})

Best Practices and Optimization

1. Test Organization

Structure your tests effectively to maintain clarity and scalability:

// Component structure
src/
  components/
    Button/
      Button.tsx
      Button.test.tsx
      Button.module.css
  pages/
    api/
      endpoint.ts
      endpoint.test.ts

2. Performance Optimization

  1. Parallel Test Execution

    // package.json
    {
      "scripts": {
        "test": "jest --maxWorkers=50%"
      }
    }
    
  2. Selective Test Running

    # Run tests matching a pattern
    npm test -- -t "button"
    
    # Run tests in watch mode
    npm test -- --watch
    
  3. Cache Configuration

    // jest.config.js
    module.exports = {
      // ... other config
      cacheDirectory: '.jest-cache',
      cache: true,
    }
    

3. Mocking Strategies

  1. Module Mocking

    jest.mock('@/utils/api', () => ({
      fetchData: jest.fn(() => Promise.resolve({ data: 'mocked' }))
    }))
    
  2. Next.js Router Mocking

    jest.mock('next/router', () => ({
      useRouter() {
        return {
          route: '/',
          pathname: '',
          query: {},
          asPath: '',
          push: jest.fn(),
          events: {
            on: jest.fn(),
            off: jest.fn()
          },
          beforePopState: jest.fn(() => null),
          prefetch: jest.fn(() => null)
        }
      }
    }))
    
  3. Environment Variables

    beforeAll(() => {
      process.env.NEXT_PUBLIC_API_URL = 'http://test-api.com'
    })
    
    afterAll(() => {
      delete process.env.NEXT_PUBLIC_API_URL
    })
    

Alternatives to Jest

Given the challenges with Jest in Next.js 15, many developers are exploring alternatives. Here's a detailed look at the most promising options:

1. Vitest

Vitest has emerged as a popular alternative, with many developers in the Next.js community reporting positive experiences.

Advantages:

  • Native ES modules support

  • Faster execution times

  • Better compatibility with modern frameworks

  • Similar API to Jest (easy migration)

Setup with Next.js:

npm install -D vitest @vitejs/plugin-react @testing-library/react jsdom
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./vitest.setup.ts'],
  },
})

Example Test:

import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import Button from './Button'

describe('Button', () => {
  it('renders correctly', () => {
    render(<Button>Click me</Button>)
    expect(screen.getByText('Click me')).toBeInTheDocument()
  })
})

2. Playwright for Component Testing

While primarily known for E2E testing, Playwright now offers component testing capabilities that some developers find more reliable than traditional unit testing approaches.

Setup:

npm install -D @playwright/test @playwright/experimental-ct-react
// playwright-ct.config.ts
import { defineConfig } from '@playwright/experimental-ct-react'

export default defineConfig({
  testDir: './tests',
  use: {
    ctPort: 3100,
    ctViteConfig: {
      resolve: {
        alias: {
          '@': '/src',
        },
      },
    },
  },
})

Example Component Test:

import { test, expect } from '@playwright/experimental-ct-react'
import Button from './Button'

test('button renders correctly', async ({ mount }) => {
  const component = await mount(<Button>Click me</Button>)
  await expect(component).toContainText('Click me')
})

Making the Decision: Jest vs Alternatives

When deciding whether to stick with Jest or migrate to an alternative, consider these factors based on community feedback and experiences:

Reasons to Stay with Jest

  1. Team Familiarity

    • Established testing patterns and knowledge

    • Extensive documentation and community support

    • Large ecosystem of plugins and tools

  2. Project Constraints

    • Legacy codebase with extensive Jest tests

    • Team resource limitations for migration

    • Dependencies on Jest-specific features

  3. Performance RequirementsAs noted in community discussions, Jest still offers competitive performance:

    # Example Jest performance optimization
    jest --maxWorkers=50% --runInBand
    

Reasons to Switch to Alternatives

  1. Modern Framework Compatibility

    • Better support for ES modules

    • Fewer configuration requirements

    • Improved TypeScript integration

  2. Development Experience

    • Faster test execution with Vitest

    • Better error messages and debugging

    • Simpler configuration

  3. Future-ProofingAs mentioned by developers in the Next.js community:

    "If you must do unit testing, Vitest is an improvement but still not great."
    

Migration Strategy

If you decide to migrate from Jest, consider this phased approach:

  1. Assessment Phase

    • Audit existing tests

    • Identify Jest-specific features in use

    • Evaluate team capacity for migration

  2. Pilot Phase

    • Start with new features using the new framework

    • Create parallel test suites for critical components

    • Document migration patterns and challenges

  3. Migration Phase

    • Gradually migrate existing tests

    • Update CI/CD pipelines

    • Maintain both test suites during transition

Conclusion

The relationship between Jest and Next.js 15 presents both challenges and opportunities. While Jest remains a viable testing solution, the growing pains of modern JavaScript development have led to the emergence of compelling alternatives like Vitest and Playwright's component testing.

Key Takeaways

  1. Jest Integration

    • Possible but requires careful configuration

    • May face compatibility issues with React 19

    • Solutions exist for common problems

  2. Alternative Frameworks

    • Vitest offers modern features and better compatibility

    • Playwright provides comprehensive testing capabilities

    • Migration should be carefully considered

  3. Decision Making

    • Consider team expertise and project requirements

    • Evaluate long-term maintenance implications

    • Factor in future framework updates

Resources for Further Learning

  1. Jest Documentation

  2. Next.js Testing Documentation

  3. Vitest Documentation

  4. Playwright Component Testing

Remember that the best testing solution is one that fits your team's needs and capabilities while ensuring code quality and maintainability. Whether you choose to stick with Jest or migrate to alternatives, maintaining a comprehensive test suite remains crucial for production-ready Next.js applications.

Raymond Yeh

Raymond Yeh

Published on 10 March 2025

Get engineers' time back from marketing!

Don't let managing a blog on your site get in the way of your core product.

Wisp empowers your marketing team to create and manage content on your website without consuming more engineering hours.

Get started in few lines of codes.

Choosing a CMS
Related Posts
Vitest vs Jest - Which Should I Use for My Next.js App?

Vitest vs Jest - Which Should I Use for My Next.js App?

Struggling with slow tests in your Next.js app? Dive into a real-world comparison of Jest vs Vitest, with actual performance metrics and migration strategies that won't give you nightmares.

Read Full Story
Setting up Vitest for Next.js 15

Setting up Vitest for Next.js 15

Learn how to set up Vitest with Next.js 15, React Testing Library, and TypeScript. Complete guide with configuration, best practices, and performance optimization tips.

Read Full Story
Testing in Next.js - What Should I Know?

Testing in Next.js - What Should I Know?

Complete guide to Next.js testing: Learn Jest, Vitest, React Testing Library, and end-to-end testing. Perfect for beginners with practical code examples and configuration steps.

Read Full Story
Loading...