0

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?

Raul
  • 167
  • 9
  • 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 Apr 11 '21 at 19:46
  • 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 Apr 11 '21 at 20:09

1 Answers1

-1

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.

Raul
  • 167
  • 9
  • 2
    You didn't return an arrow in the OP, you returned Jest spy. There shouldn't be a difference between `return jest.fn().mockImplementation(` and what you posted, unless you reset mocks somewhere (and you shouldn't do this). – Estus Flask Apr 11 '21 at 22:13
  • @EstusFlask, are you saying that the OP should have worked? When you say that "I should do this" are you talking about returning a function() or reseting the mocks? I really don't know the right way to do it, I followed the docs example and it didn't work. – Raul Apr 11 '21 at 22:57
  • I cannot say if it's 100% workable, but the code in the answer isn't more correct than the code in the question. I mean the latter, double check there are no `*reset*` things in tests and Jest config. But by returning a function you prevent yourself from using Jest spies, which are useful. – Estus Flask Apr 11 '21 at 23:03