jest.spyOn() TypeError when called on react hook

Viewed 2195

I'm trying to use jest.spyOn() for my tests instead of jest.mock since the return value of a mock can't be type-checked. When I use it though, I get the following error:

TypeError: Cannot set property useGame of #<Object> which has only a getter
> 3 | const STATE_SPY = jest.spyOn(hooks, "useGame");
    |                        ^ 
at ModuleMockerClass.spyOn (node_modules/jest-mock/build/index.js:852:26)
at Object.<anonymous> (src/components/game/game.test.tsx:4:24)  

My files are structured like:

src/
  - game.test.tsx
  - game.tsx
  - hooks/
    - index.tsx
    - use-game.tsx

And here is how my code is laid out (I removed a lot of extraneous code for this question to make it easier to read).

/hooks/index.tsx:

export { default as useGame } from "./use-game";

use-game.tsx:

export default function useGame(initScore: number) {
  const [score, setScore] = useState(initScore);

  return {
    score,
    setScore,
  }
}

game.test.tsx:

import * as hooks from "./hooks";

const STATE_SPY = jest.spyOn(hooks, "useGame");

game.tsx:

import React from "react";
import { useGame } from "./hooks";

const STARTING_SCORE = 50;

export default function Game(): JSX.Element {
  const {score} = useGame(STARTING_SCORE);

  return (
    <div>
      {score}
    </div>
  )
}

I think the issue is how useGame is exported as a module. But I haven't figured out a way to export/import it "correctly". Of course, there could be another issue that I'm unaware of.

App was created with create-react-app.

My package.json in case that helps.

{
  "name": "greatest-game-of-all-time",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^4.11.0",
    "@material-ui/icons": "^4.9.1",
    "@types/jest": "^24.9.1",
    "@types/node": "^12.12.53",
    "@types/react": "^16.9.43",
    "@types/react-dom": "^16.9.8",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-hook-form": "^6.1.2",
    "react-scripts": "^3.4.1",
    "typescript": "^3.9.7"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@testing-library/dom": "^7.21.5",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/react-hooks": "^3.4.1",
    "@testing-library/user-event": "^7.2.1",
    "prettier": "2.0.5",
    "react-test-renderer": "^16.13.1"
  }
}
2 Answers

It seems you use babel to transpile your code. As I know that if you import with wild card (namespace) like that. It will create a object contains your named exports as getter like

var obj = { get namedExport: () => ... }

so in this case, I suggest to either mock entire module:

jest.mock('./hooks', () => ({ useGame: yourMock }))

or switch to tsc by using ts-jest to fix your issue (since tsc won't use getter to export that named export)

Related