Note: I have created a full paired down Github repo that you can download and checkout for yourself here
I am trying to mock out a non-default exported class in a module using a manual mock in the folder __mocks__ directly adjacent to the file. This works fine and I can get the mock to load correctly in my tests. However, if I export the mocked function as described here in the docs it doesn't seem to actually give me a reference to the function that the mocked class calls in the test. This means when I call
expect(getAppDetailMock).toHaveBeenCalledTimes(1);
It fails as it has not been called at all.
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
My suspicion is that it has something to do with the class itself ending up with a new version of the function, and not the very same reference I am importing... I say this because I did the exact same pattern to mock out a package in my node_modules which was not a class and it worked perfectly fine.
Although you can check out the full code example here on github I have copied my files below for full clarity. Any ideas on why this is not working as I would expect?
// api/__mocks__/clients.ts
export const getAppDetailMock = jest.fn();
export const AppsClient = jest.fn().mockImplementation(() => {
return {
getAppDetail: getAppDetailMock,
};
});
// App.test.js
import React from "react";
import {
render,
screen,
waitForElementToBeRemoved,
} from "@testing-library/react";
import App from "./App";
import { getAppDetailMock } from "./api/__mocks__/clients";
jest.mock("./api/clients");
describe("<App /> ", () => {
beforeEach(() => {
getAppDetailMock.mockReset();
});
test("Should show not found message if app does not exist", async () => {
const appId = "8500f5dd-8b41-4cb8-95fa-246b1f25855b";
getAppDetailMock.mockResolvedValue(null);
render(<App appId={appId} />);
await waitForElementToBeRemoved(() => screen.getByText("Loading"));
expect(getAppDetailMock).toHaveBeenCalledTimes(1);
expect(getAppDetailMock).toHaveBeenCalledWith(appId);
expect(screen.getByText(`App not found found`)).toBeInTheDocument();
});
test("Should show not found message if app does not exist", async () => {
const appId = "8500f5dd-8b41-4cb8-95fa-246b1f25855b";
getAppDetailMock.mockResolvedValue({ appId });
render(<App appId={appId} />);
await waitForElementToBeRemoved(() => screen.getByText("Loading"));
expect(getAppDetailMock).toHaveBeenCalledTimes(1);
expect(getAppDetailMock).toHaveBeenCalledWith(appId);
expect(
screen.getByText(`App found with id '${appId}'`)
).toBeInTheDocument();
});
});
// App.tsx
import React, { useState, useEffect } from "react";
import "./App.css";
import { AppsClient, AppDetailDto } from "./api/clients";
type AppProps = {
appId: string;
};
const App: React.FC<AppProps> = ({ appId }) => {
const [hasLoaded, setHasLoaded] = useState(false);
const [appDetail, setAppDetail] = useState<AppDetailDto | null>(null);
useEffect(() => {
const getAppDetail = async (appId: string) => {
try {
setHasLoaded(false);
const result = await new AppsClient().getAppDetail(appId);
setAppDetail(result);
setHasLoaded(true);
} catch (error) {}
};
if (!hasLoaded) {
getAppDetail(appId);
}
}, [appId, hasLoaded]);
if (!hasLoaded) {
return <>Loading</>;
} else if (!appDetail) {
return <>App not found found</>;
}
return <>App found with id '{appDetail.appId}'</>;
};
export default App;
// api/clients.ts
import { BaseClient } from "./baseClient";
export interface AppDetailDto {
appId: string;
}
export class AppsClient extends BaseClient {
getAppDetail(appId: string): Promise<AppDetailDto> {
return Promise.resolve({ appId });
}
}