0
votes

I'm trying to mock an service class to test an React component. But the module factory from jest.mock is not working.

Search component:

import React, { useState } from "react";
import SearchService from "../../services/SearchService";

export default function Search() {
  const [searchResults, setSearchResults] = useState([]);

  function doSearch() {
    const service = new SearchService();
    service.search().then(setSearchResults);
  }

  return (
    <div className="component-container">
      <div>
        <button onClick={doSearch}>search</button>
      </div>
      {searchResults.map((result) => (
        <div key={result}>{result}</div>
      ))}
    </div>
  );
}

SearchService:

export default class SearchService {
  search = function () {
    return new Promise((resolve) => {
      setTimeout(
        () => resolve(["result 1", "result 2", "result 3", "result 4"]),
        1000
      );
    });
  };
}

Test file:

import React from "react";
import { screen, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { act } from "react-dom/test-utils";
import Search from "../features/search/Search";

jest.mock("../services/SearchService", () => {
  return jest.fn().mockImplementation(() => {
    return { search: jest.fn().mockResolvedValue(["mock result"]) };
  });
});

test("Search", async () => {
  render(<Search />);
  const button = screen.getByRole("button");
  expect(button).toBeDefined();
  act(() => {
    userEvent.click(button);
  });
  await screen.findByText("mock result");
});

This is the same structure as the Jest documentation example. In the code above I'm passing the mock implementation through the module factory parameter of the jest.mock. But it does not work. When I log the new SerchService() I get "mockConstructor {}" and when I run the test it throws the error "service.search is not a function".

When I change my test file to...

import React from "react";
import { screen, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { act } from "react-dom/test-utils";
import Search from "../features/search/Search";
import SearchService from "../services/SearchService";

jest.mock("../services/SearchService");

test("Search", async () => {
  SearchService.mockImplementation(() => {
    return { search: jest.fn().mockResolvedValue(["mock result"]) };
  });
  render(<Search />);
  const button = screen.getByRole("button");
  expect(button).toBeDefined();
  act(() => {
    userEvent.click(button);
  });
  await screen.findByText("mock result");
});

It works... I kinda can understand why it works in the second way, it is like using jest.spyOn I guess. What I cant understand is why it doesnt work with the first approach.

What I'm doing wrong? How can I mock a module implementation with jest.mock without calling .mockImplementation inside each test?

1
Why is the service a class to begin with? Just export a function, there's no state. In general things shouldn't new up their collaborators because then they become very coupled.jonrsharpe
Hi @jonrsharpe, this is not a real project, I'm trying to learn about unit test so I created this fake service as a class to try to mock a class.Raul

1 Answers

-1
votes

I found that there is a problem with the documentation and that the factory needs to return an function() (not an arrow function), so I changed the mock to the following and it works:

jest.mock("../services/SearchService.js", () => {
  return function () {
    return { search: jest.fn().mockResolvedValue(["mock result"]) };
  };
});

Found on this post.