Home > database >  Jest - How to mock a ES6 default export and then Change the default implementation/return value
Jest - How to mock a ES6 default export and then Change the default implementation/return value

Time:09-11

Background

So I am trying to test a React functional component that calls a custom hook and depending on the return value of the hook, the component will render something differently. See below for an example:


// App.js

import "./styles.css";
import useCustomHook from "./useHook";

export default function App() {
  const [state, handler] = useCustomHook();

  return state ? <div>state is true</div> : <div>state is false</div>;
}

// useHook.js
import { useState } from "react";

const useCustomHook = () => {
  const [myState, setMyState] = useState(false);

  const handleSetMyState = () => {
    return setMyState((prevState) => !prevState);
  };

  return [myState, handleSetMyState];
};

export default useCustomHook;


and finally, my test:

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

import useCustomHook from './useHook'; 

jest.mock("./useHook", () => ({
  __esModule: true,
  default: () => [false, jest.fn()]
}));

describe("testing App state", () => {
  afterEach(() => {
    jest.clearAllMocks();
    cleanup();
  });

  it("should render false when state is false", () => {
    // initialized as false AND mocked returned as false
    render(<App />);
    expect(screen.queryByText(/false/i)).toBeInTheDocument();
  });
  it("should render true when state is true", () => {
    // the following doesn't work
    useCustomHook.mockImplementation(() => [ true, jest.fn() ]) // outputs error: TypeError: _useCustomHook.default.mockImplementation is not a function

    render(<App />);
    expect(screen.queryByText(/true/i)).toBeInTheDocument();
  });
});


Now the test would pass in my local environment ( unfortunately codesandbox's environment has issues with jest.mock, so I cannot provide a live sample ) but for the next test case, if the state returned is true the component should render text with "true" in it.

Issue

However, I am unable to change the mocked implementation or the returned value of the mock for it to return something like [true, jest.fn()]

So I have been stuck on this issue for some time now and cannot find any relatable resource online that fits as a solution. I have tried the following and none worked.

  • importing the custom hook in test and changing its mocked implementation ( like shown in example )
  • same as above but changing its mockReturnValueOnce to [ true, jest.fn() ]
  • adding a __mocks__ directory in the same lvl as the hook and having a file with the same name __mocks__/useHook.js, and mock the whole module by jest.mock('./useHook') instead. Similar to this solution.
  • (updated, also tried this as latest attempt to mock) jest.mock('./useHook', () => ({ __esModule: true, default: {useCustomHook: jest.fn()}})) and it still outputs the same error when I tried to change the implementation TypeError: _useCustomHook.default.mockImplementation is not a function

Please help, something as simple as changing a mocked returned value is done so easily with other languages and testing frameworks. Why is jest complaining about?

CodePudding user response:

The default export is not a mock function, you should use jest.fn() to create a mock function with a mock implementation.

jest.mock('./useHook', () => ({
  __esModule: true,

  // It should be
  default: jest.fn(() => [false, jest.fn()]),

  // NOT
  // default: () => [false, jest.fn()]
}));

But test implementation details are not encouraged. See What you should avoid with Testing Library

Testing Library encourages you to avoid testing implementation details like the internals of a component you're testing (though it's still possible). The Guiding Principles of this library emphasize a focus on tests that closely resemble how your web pages are interacted by the users.

You may want to avoid the following implementation details:

  1. Internal state of a component
  2. Internal methods of a component
  3. Lifecycle methods of a component
  4. Child components

CodePudding user response:

I was able to find a workaround instead.


// test.js
import * as useCustomHook from './useHook';

// then inside my individual test

it('should render true when state is true"', () => {
  const mockHook = jest.spyOn(useCustomHook, 'default');
  mockHook.mockImplementation(() => [true, jest.fn()]);
  render(<App />);
  expect(screen.queryByText(/true/i)).toBeInTheDocument();
});


Not sure why jest.mock doesn't work, but I was able to make it work with jest.spyOn. If anyone is able to shed some light on why one method worked and the other didn't, it would be greatly appreciated.

  • Related