skip to Main Content

I just started a new React/Next.js project and realized that I cannot mock memoized React components the way I could in a previous project. And I fail to find out why. Here’s a minimal example of my setup:

// /src/components/Foo.tsx

import Bar from "./Bar";

const Foo = () => (
  <div>
    <Bar />
  </div>
);
export default Foo;
// /src/components/Bar.tsx

import { memo } from "react";

const Bar = memo(function Bar_() {
  return <div>Original</div>;
});

export default Bar;

Now I want to test Foo mocking Bar.

This is what I did in my last project:

// /src/components/Foo.test.tsx

import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import Foo from "./Foo";

const barModule = require("./Bar");
jest.replaceProperty(barModule, "default", () => <div>Mocked</div>);

describe("Foo", () => {
  it("should mock Bar", () => {
    render(<Foo />);

    expect(screen.queryByText("Original")).not.toBeInTheDocument();
  });
});

But when I run the tests, I get the error message "Property default is not declared configurable". How can I fix this?

Here are my config files:

// jest.config.js

const nextJest = require("next/jest");

/** @type {import('jest').Config} */
const createJestConfig = nextJest({
  dir: "./",
});

/** @type {import('jest').Config} */
const config = {
  clearMocks: true,
  collectCoverage: true,
  coverageDirectory: "coverage",
  coverageProvider: "v8",
  testEnvironment: "jsdom",
};

module.exports = createJestConfig(config);
// tsconfig.json

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
// package.json

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "test": "jest"
  },
  "dependencies": {
    "next": "14.1.4",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.4.2",
    "@testing-library/react": "^14.2.2",
    "@types/jest": "^29.5.12",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "^14.1.4",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

2

Answers


  1. Mock like this:

    jest.mock('./Bar', () => ({ default: () => 'mocked Bar' }));
    
    Login or Signup to reply.
  2. Since ES modules have a static structure and are not designed to allow their exports to be modified at runtime, trying to mock them directly with methods like jest.replaceProperty can lead to errors, especially with the error message "Property default is not declared configurable" which you’re encountering. This behavior is because the exports of ES modules are sealed and cannot be reconfigured, which includes making them non-configurable or non-writable, as required by the Jest mocking mechanism.

    Use jest.mock with Module Factory Parameter

    jest.mock('./Bar', () => {
      return {
        __esModule: true, // This property makes it behave like an ESModule
        default: () => <div>Mocked</div>,
      };
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search