Mock method from external file called by a React component - reactjs

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' });

Related

Jest Mock returns undefined instead of value

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

FE: Unit testing window.location.href changing on button click (test fails)

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).

Problems testing a Redux + React app with enzyme:

I have this component
import React, { useEffect } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { CircularProgress } from '#material-ui/core';
import { loadPhones } from './redux/actions/actions.js';
import TablePhones from './Table.js';
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadPhones: () => {
dispatch(loadPhones());
},
};
}
export function App(props) {
useEffect(() => {
props.loadPhones();
}, []);
if (props.phones.data) {
return (
<div className="App">
<div className="introductoryNav">Phones</div>
<TablePhones phones={props.phones.data} />
</div>
);
}
return (
<div className="gridLoadingContainer">
<CircularProgress color="secondary" iconStyle="width: 1000, height:1000" />
<p className="loadingText1">Loading...</p>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
For whom ive written
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from "react-redux";
import App from './App';
import { shallow, mount } from "enzyme";
import configureMockStore from "redux-mock-store";
const mockStore = configureMockStore();
const store = mockStore({});
describe('App comp testing', () => {
it("should render without throwing an error", () => {
const app = mount(
<Provider store={store}>
<App />
</Provider>
).dive()
expect(app.find('.introductoryNav').text()).toContain("Phones");
});
})
But that test keeps failing
ypeError: Cannot read property 'data' of undefined
I also tried importing App as {App} instead and using shallow testing, but no luck. It gives the same erros, so im left without access to the context, and I cant keep doing my tests
How can I solve this?
You could use the non-default export of your component here and shallow render test if you pass your component the props and don't try to mock the store (if I recall correctly).
I was thinking something like this might work, tesing the "pure" non-store connected version of the component. This seems to be a popular answer for this question as this was asked (in a different way) before here:
import React from 'react';
import { App } from './App';
import { shallow } from "enzyme";
// useful function that is reusable for desturcturing the returned
// wrapper and object of props inside of beforeAll etc...
const setupFunc = overrideProps => {
const props = {
phones: {
...phones, // replace with a mock example of a render of props.phones
data: {
...phoneData // replace with a mock example of a render of props.phones.data
},
},
loadPhones: jest.fn()
};
const wrapper = shallow(<App {...props} />);
return {
wrapper,
props
};
};
// this is just the way I personally write my inital describe, I find it the easiest way
// to describe the component being rendered. (alot of the things below will be opinios on test improvements as well).
describe('<App />', () => {
describe('When the component has intially rendered' () => {
beforeAll(() => {
const { props } = setupFunc();
});
it ('should call loadPhones after the component has initially rendered, () => {
expect(props.loadPhones).toHaveBeenCalled();
});
});
describe('When it renders WITH props present', () => {
// we should use describes to section our tests as per how the code is written
// 1. when it renders with the props present in the component
// 2. when it renders without the props
beforeAll(() => {
const { wrapper, props } = setupFunc();
});
// "render without throwing an error" sounds quite broad or more like
// how you would "describe" how it rendered before testing something
// inside of the render. We want to have our "it" represent what we're
// actually testing; that introductoryNave has rendered with text.
it("should render an introductoryNav with text", () => {
// toContain is a bit broad, toBe would be more specific
expect(wrapper.find('.introductoryNav').text()).toBe("Phones");
});
it("should render a TablePhones component with data from props", () => {
// iirc toEqual should work here, you might need toStrictEqual though.
expect(wrapper.find('TablePhones').prop('phones')).toEqual(props.phones);
});
});
describe('When it renders WITHOUT props present', () => {
it("should render with some loading components", () => {
expect(wrapper.find('.gridLoadingContainer').exists()).toBeTruthy();
expect(wrapper.find('CircularProgress').exists()).toBeTruthy();
expect(wrapper.find('.loadingText1').exists()).toBeTruthy();
});
});
});

Testing custom hook: Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

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

Context API Unit test failing with TypeError

I have a HOC component WithContext (in a file conveniently named withContext.js) as follows
import React from 'react';
import { DEFAULT_STATE } from './store';
const MyContext = React.createContext(DEFAULT_STATE);
export function WithContext(Component) {
return function WrapperComponent(props) {
return (
<MyContext.Consumer>
{state => <Component {...props} context={state} />}
</MyContext.Consumer>
);
};
};
and a component that uses it as follows
import React from "react";
import { WithContext } from './withContext';
const MyComp = (context) => {
return (
<div className="flex dir-col" id="MyComp">
<p>This is a test</p>
</div>
)
};
export default WithContext(MyComp);
I also have a unit test that aims to test this MyComp component. The unit test follows
import React from "react";
import {shallow} from "enzyme";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { WithContext } from './withContext';
// We need to configure our DOM
import jsdom from 'jsdom'
const {JSDOM} = jsdom;
const {document} = (new JSDOM('<!doctype html><html><body></body></html>')).window;
global.document = document;
global.window = document.defaultView
Enzyme.configure({
adapter : new Adapter()
})
beforeEach(() => {
jest.resetModules()
})
//Takes the context data we want to test, or uses defaults
const getMyContext = (context = {
state : {}
}) => {
// Will then mock the MyContext module being used in our MyComp component
jest.doMock('withContext', () => {
return {
MyContext: {
Consumer: (props) => props.children(context)
}
}
});
// We will need to re-require after calling jest.doMock.
// return the updated MyComp module that now includes the mocked context
return require('./MyComp').default;
};
describe("MyComp component loading check", () => {
test("Renders the MyComp component correctly", () => {
const MyCompContext = getMyContext();
const wrapper = shallow(<MyComp/>);
// make sure that we are able to find the header component
expect(wrapper.find(".flex").hostNodes().length).toBe(1);
});
});
However this test keeps failing with the message
TypeError: (0 , _withContext.WithContext) is not a function
};
export default WithContext(MyComp);
Can you please let me know what is wrong here ?
Thanks
Looks like you are mocking withContext with jest.doMock but the mock you are returning from the factory function does not contain a WithContext function.
Then when you require('./MyComp').default it is using the withContext mock within your MyComp module and failing when it tries to export default WithContext(MyComp); since the withContext mock does not define a WithContext function.

Resources