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 });
}
}
Try checking to see if your getAppDetail has been called instead.
so something more like:
expect(getAppDetail).toHaveBeenCalledTimes(1);
Or, if you can, pull in the entire mocked component and check that it's specific getAppDetail has been called, like:
expect(AppsClient.getAppDetail).toHaveBeenCalledTimes(1);
Your suspicion is probably correct... the fact that you're importing a separate instance of the getAppDetailMock, that instance of it won't have been called, as it's a separate import not related to the AppsClient.getAppDetail mock.
Try import direct from ./api/clients with #ts-ignore
// #ts-ignore
import { getAppDetailMock } from "./api/clients";
or
import * as Clients from './api/clients';
import * as ClientsMock from './api/__mocks__/clients';
jest.mock("./api/clients");
const {
getAppDetailMock
} = (Clients as unknown) as typeof ClientsMock;
Related
I have a React component which is calling methods defined in another file, something like this:
import { realMethod } from '../some-library';
export const MyComponent = () => {
const result = realMethod();
const field = result.someField;
/* Rest of the code... */
}
I want to write a test for MyComponent using react-testing-library and it's breaking because when it comes to the line const field = result.someField it doesn't know what it is (TypeError: Cannot read properties of undefined (reading 'someField').
So far I wrote this:
import * as React from 'react';
import { render, screen, userEvent, within } from '../../test-utils';
import { MyComponent } from './MyComponent';
import * as realMethods from '../some-library';
describe('<MyComponent />', () => {
it('should do stuff', () => {
const spyMethod = jest.spyOn(
realMethods,
'realMethod',
);
render(
<MyComponent />,
);
/* ... */
});
});
How can I mock realMethod and make it return something useful?
The spyOn itself shouldn't break your code. It looks like your realMethod does not return an object that contain someField, e.g.:
export const realMethod = () => ({ someField: 42 })
You can mock it like this:
jest.spyOn(realMethods, 'realMethod').mockReturnValue({ someField: 'mocked' });
I am using Jest to test a react component. I am trying to mock a function from other dependency. The function from dependency should return an array, but it is showing undefined on the console.
Below file is the tsx file, when I click the button, it should call the dependency function to get the list of the Frames.
ExitAppButton.tsx:
import React, { useContext, useState } from 'react';
import { TestContext } from '../ContextProvider';
import { useDispatch } from 'react-redux';
const ExitAppButton = (props: any): JSX.Element => {
const { sdkInstance } = useContext(TestContext);
const exitAppClicked = () => {
const appList = sdkInstance.getFrames().filter((app: any) => {app.appType === "Test App"}).length}
test file, SignOutOverlay.test.tsx:
import * as React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import SignOutOverlay from '.';
import ExitAppButton from './ExitAppButton';
import { TestContext } from '../ContextProvider';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
const api = require('#praestosf/container-sdk/src/api');
const mockStore = configureStore([]);
jest.mock('#praestosf/container-sdk/src/api');
api.getFrames.mockReturnValue([{appType:"Test App"},{appType:"Test App"},{appType:"Not Test App"}]);
describe('Test Exit app Button', () => {
const renderExitAppButton = () => {
const store = mockStore([{}]);
render(
<Provider store={store}>
<TestContext.Provider value={{ sdkInstance: api }}>
<SignOutOverlay>
<ExitAppButton/>
</SignOutOverlay>
</TestContext.Provider>
</Provider>
);
};
it('should to be clicked and logged out', () => {
renderExitAppButton();
fireEvent.click(screen.getByTestId('exit-app-button-id'));
});
This is the dependency file, api.js
const getFrames = () => {
let frames = window.sessionStorage.getItem('TestList');
frames = frames ? JSON.parse(frames) : [];
return frames
};
const API = function () { };
API.prototype = {
constructor: API,
getFrames
};
module.exports = new API();
I mocked the getFrame function to return an array of 3 objects, but when running the test case, it is returning undefined. Below error was showing:
TypeError: Cannot read property 'filter' of undefined
Am I mocking this correct?
I think it's because api.getFrames is undefined and not a mock.
Try changing your mock statement to this:
jest.mock('#praestosf/container-sdk/src/api', () => ({
getFrames: jest.fn(),
// add more functions if needed
}));
Turns out, I have the other file with the same test name which is causing the problem. I am beginner for Jest, a tip for developer like me, we should always run test case file alone using
jest file.test.tsx
Not all files at a time:
jest
I am trying to test when the window.location.href changes after a button is clicked using react-testing-library. I have seen examples online where you manually update window.location.href inside of a test case as so window.location.href = 'www.randomurl.com' and then follow it with expect(window.location.href).toEqual(www.randomurl.com). While this indeed will pass, I want to avoid this as I'd rather simulate the user actions instead of injecting the new value into the test. If I do that, even if I remove my button click (which is what will actually trigger the function call) the expect will still pass because I have anyway manually updated the window.location.href in my test
What I've opted for is having goToThisPage func (which will redirect the user) to be placed outside of my functional component. I then mock goToThisPage in my test file and in my test case check whether it has been called. I do know that the goToThisPage is being triggered because I included a console.log and when I run my tests I see it in my terminal. Nonetheless, the test still fails. I have been playing around with both spyOn and jest.doMock/mock with no luck
component.js
import React from 'react'
import { ChildComponent } from './childcomponent';
export const goToThisPage = () => {
const url = '/url'
window.location.href = url;
console.log('reached');
};
export const Component = () => {
return (<ChildComponent goToThisPage={ goToThisPage }/>)
}
export default Component;
Test file:
import * as Component from './component'
import userEvent from '#testing-library/user-event';
jest.doMock('./component', () => ({
goToThisPage: jest.fn(),
}));
describe('goToThisPage', () => {
test('should call goToThisPage when button is clicked', async () => {
const goToThisPageSpy = jest.spyOn(Component, 'goToThisPage');
const { container, getByTestId } = render(<Component.Component />);
userEvent.click(screen.getByTestId('goToThisPage')); // this is successfully triggered (test id exists in child component)
expect(goToThisPageSpy).toHaveBeenCalled();
// expect(Component.goToThisPage()).toHaveBeenCalled(); this will fail and say that the value must be a spy or mock so I opted for using spy above
});
});
Note: when I try to just do jest.mock I got this error Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
When testing out with jest.doMock the error disappeared but the actual test fails.
I am open to hear more refined ideas of solving my issue if someone believes this solution could be improved. Thanks in advance
Edit:
This is another approach I have tried out
import { Component, goToThisPage } from './component'
import userEvent from '#testing-library/user-event';
describe('goToThisPage', () => {
test('should call goToThisPage when button is clicked', async () => {
const goToThisPageSpy = jest.spyOn(Component, 'goToThisPage');
// I am not certain what I'd put as the first value in the spy. Because `goToThisPage` is an external func of <Component/> & not part of the component
const { container, getByTestId } = render(<Component />);
userEvent.click(screen.getByTestId('goToThisPage'));
expect(goToThisPageSpy).toHaveBeenCalled();
});
});
Save yourself the headache and split the goToThisPage function into its own file. You seem to be mocking the goToThisPage function fine but when the Component is rendered with react testing library it doesn't seem render with the mocked function but defaults to what the function would normally do. This easiest way would be just to mock the function from its own file. If you truly want to keep the function in the same file you will need to make some adjustments, see (example #2) but I do not recommend this path.
See below for examples
Example 1: (Recommended) Split function into it's own file
Component.spec.jsx
import React from "react";
import Component from "./Component";
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import * as goToThisPage from "./goToThisPage";
jest.mock('./goToThisPage');
describe("goToThisPage", () => {
test("should call goToThisPage when button is clicked", async () => {
const goToThisPageSpy = jest.spyOn(goToThisPage, 'default').mockImplementation(() => console.log('hi'));
render(<Component />);
userEvent.click(screen.getByTestId("goToThisPage"));
expect(goToThisPageSpy).toHaveBeenCalled();
});
});
goToThisPage.js
export const goToThisPage = () => {
const url = "/url";
window.location.href = url;
};
export default goToThisPage;
Component.jsx
import React from "react";
import ChildComponent from "./ChildComponent";
import goToThisPage from "./goToThisPage";
export const Component = () => {
return <ChildComponent goToThisPage={goToThisPage} />
};
export default Component;
Example 2: (Not Recommend for React components!)
We can also get it working by calling the goToThisPage function via exports. This ensures the component is rendered with our spyOn and mockImplementation. To get this working for both browser and jest you need to ensure we run the original function if it's on browser. We can do this by creating a proxy function that determines which function to return based on a ENV that jest defines when it runs.
Component.jsx
import React from "react";
import ChildComponent from "./ChildComponent";
export const goToThisPage = () => {
const url = "/url";
window.location.href = url;
};
// jest worker id, if defined means that jest is running
const isRunningJest = !!process.env.JEST_WORKER_ID;
// proxies the function, if jest is running we return the function
// via exports, else return original function. This is because
// you cannot invoke exports functions in browser!
const proxyFunctionCaller = (fn) => isRunningJest ? exports[fn.name] : fn;
export const Component = () => {
return <ChildComponent goToThisPage={proxyFunctionCaller(goToThisPage)} />
};
export default Component;
Component.spec.jsx
import React from "react";
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
describe("goToThisPage", () => {
test("should call goToThisPage when button is clicked", async () => {
const Component = require('./Component');
const goToThisPageSpy = jest.spyOn(Component, 'goToThisPage').mockImplementation(() => console.log('hi'));
render(<Component.default />);
userEvent.click(screen.getByTestId("goToThisPage"));
expect(goToThisPageSpy).toHaveBeenCalled();
});
});
You can move the function proxy to it's own file but you need to pass exports into the proxy function as exports is scoped to it's own file.
Example code
// component.js
import React from "react";
import ChildComponent from "./ChildComponent";
import proxyFunctionCaller from "./utils/proxy-function-caller";
export const goToThisPage = () => {
const url = "/url";
window.location.href = url;
};
export const Component = () => {
return <ChildComponent goToThisPage={proxyFunctionCaller(typeof exports !== 'undefined' ? exports : undefined, goToThisPage)} />
};
export default Component;
// utils/proxy-function-caller.js
// jest worker id, if defined means that jest is running
const isRunningJest = !!process.env.JEST_WORKER_ID;
// proxies the function, if jest is running we return the function
// via exports, else return original function. This is because
// you cannot invoke exports functions in browser!
const proxyFunctionCaller = (exports, fn) => isRunningJest ? exports[fn.name] : fn;
export default proxyFunctionCaller;
There are other ways to do this but I would follow the first solution as you should be splitting utility functions into it's own files anyway. Goodluck.
Example 3 for #VinceN
You can mock a function that lives in the same file using the below example files.
SomeComponent.tsx
import * as React from 'react';
const someFunction = () => 'hello world';
const SomeComponent = () => {
return (
<div data-testid="innards">
{someFunction()}
</div>
)
}
export default SomeComponent;
SomeComponent.spec.tsx
import SomeComponent from './SomeComponent';
import { render, screen } from "#testing-library/react";
jest.mock('./SomeComponent', () => ({
__esModule: true,
...jest.requireActual('./SomeComponent'),
someFunction: jest.fn().mockReturnValue('mocked!')
}));
describe('<SomeComponent />', () => {
it('renders', () => {
render(<SomeComponent />);
const el = screen.getByTestId('innards');
expect(el.textContent).toEqual('mocked!');
});
});
You exporting both functions and then defining a default export of the Component itself is what's causing the problem (which is mixing up default and named exports).
Remove export default Component; and change the top import in your test file to import {Component, goToThisPage} from './component'. That said I'm not sure you even need to export goToThisPage (for the Jest test at least).
I got a custom hook which I want to test. It receives a redux store dispatch function and returns a function. In order to get the result I'm trying to do:
const { result } = renderHook(() => { useSaveAuthenticationDataToStorages(useDispatch())});
However, I get an error:
Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a
It happens because of the useDispatch and that there is no store connected. However, I don't have any component here to wrap with a provider.. I just need to test the hook which is simply saving data to a store.
How can I fix it?
The react hooks testing library docs go more into depth on this. However, what we essentially are missing is the provider which we can obtain by creating a wrapper. First we declare a component which will be our provider:
import { Provider } from 'react-redux'
const ReduxProvider = ({ children, reduxStore }) => (
<Provider store={reduxStore}>{children}</Provider>
)
then in our test we call
test("...", () => {
const store = configureStore();
const wrapper = ({ children }) => (
<ReduxProvider reduxStore={store}>{children}</ReduxProvider>
);
const { result } = renderHook(() => {
useSaveAuthenticationDataToStorages(useDispatch());
}, { wrapper });
// ... Rest of the logic
});
This is probably a late answer but you can also use this in your test
jest.mock('react-redux', () => {
const ActualReactRedux = jest.requireActual('react-redux');
return {
...ActualReactRedux,
useSelector: jest.fn().mockImplementation(() => {
return mockState;
}),
};
});
This issues is related your test file. You have to declarer provider and store in your test file.
Update or replace your app.test.tsx by below code
NB: Don't forget to install redux-mock-store if you don't have already.
import React from 'react';
import { render } from '#testing-library/react';
import App from './App';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
describe('With React Testing Library', () => {
const initialState = { output: 10 };
const mockStore = configureStore();
let store;
it('Shows "Hello world!"', () => {
store = mockStore(initialState);
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>
);
expect(getByText('Hello World!')).not.toBeNull();
});
});
I got this solution after searching 1 hours.
Thanks a lot to OSTE
Original Solution: Github issues/8145 and solution link
With this solution if you get error like TypeError: window.matchMedia is not a function then solve by this way. add those line to your setupTests.ts file. Original solution link stackoverflow.com/a/64872224/5404861
global.matchMedia = global.matchMedia || function () {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
};
};
I think you can create test-utils.[j|t]s(?x), or whatever you set the name of the file to, like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/test-utils.tsx
//root(or wherever your the file)/test-utils.tsx
import React from 'react';
import { render, RenderOptions } from '#testing-library/react';
import { Provider } from 'react-redux';
// Import your store
import { store } from '#/store';
const Wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
const customRender = (ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: Wrapper, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
Use it like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/pages/index.test.tsx
//__tests__/pages/index.test.tsx
import React from 'react';
import { render, screen } from '../test-utils';
import Home from '#/pages/index';
describe('Home Pages', () => {
test('Should be render', () => {
render(<Home />);
const getAText = screen.getByTestId('welcome');
expect(getAText).toBeInTheDocument();
});
});
Works for me.
screenshot work
BTW, if you place the test-utils.[j|t]s(?x) or whatever you set the name file place on the directory __test__, don't forget to ignore it on jest.config.js.
//jest.config.js
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/', '<rootDir>/__tests__/test-utils.tsx'],
repo: https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart
I'm trying to test component method, which inside performing network call to external resources. After reading docs I still can't figure out how to do so. Can anyone help? Here is my code(some parts hidden for brevity):
My component:
import React from 'react'
import ResourceService from '../../modules/resource-service'
export default class SliderComponent extends React.Component {
setActiveSlide = (activeSlide) => {
ResourceService.getData({
id: activeSlide,
}).then((data) => {
if (data) {
this.setState({
data,
})
}
})
}
}
Resource service:
import axios from 'axios'
export default class ResourceService {
static getData(params) {
return axios.post('/api/get_my_data', params)
.then((resp) => resp.data)
}
}
Desired test (as I understand it):
import React from 'react'
import { mount, configure } from 'enzyme'
import SliderComponent from '../../../app/components/slider'
test('SliderComponent changes active slide when setActiveSlide is
called', () => {
const wrapper = mount(
<SliderComponent />
);
wrapper.instance().setActiveSlide(1);
// some state checks here
});
I need mock ResourceService.getData call inside SliderComponent, and I really can't understand ho to do it...
You can import your ResourceService in your test and mock the method getData with jest.fn(() => ...). Here is an example:
import React from 'react'
import { mount, configure } from 'enzyme'
import ResourceService from '../../../modules/resource-service'
import SliderComponent from '../../../app/components/slider'
test('SliderComponent changes active slide when setActiveSlide is
called', () => {
// you can set up the return value, you can also resolve/reject the promise
// to test different scnarios
ResourceService.getData = jest.fn(() => (
new Promise((resolve, reject) => { resolve({ data: "testData" }); }));
const wrapper = mount(<SliderComponent />);
wrapper.instance().setActiveSlide(1);
// you can for example check if you service has been called
expect(ResourceService.getData).toHaveBeenCalled();
// some state checks here
});
try using axios-mock-adapter to mock the postreq in your test.
It should look something like this (may need a few more tweaks):
import React from 'react'
import { mount, configure } from 'enzyme'
import SliderComponent from '../../../app/components/slider'
import axios from'axios';
import MockAdapter = from'axios-mock-adapter';
test('SliderComponent changes active slide when setActiveSlide is
called', () => {
let mock = new MockAdapter(axios)
//you can define the response you like
//but your params need to be accordingly to when the post req gets called
mock.onPost('/api/get_my_data', params).reply(200, response)
const wrapper = mount(
<SliderComponent />
);
wrapper.instance().setActiveSlide(1);
// some state checks here
});
make sure to check the docs of axios-mock-adapter