Jest mock function not being called - reactjs

My component code is as below. Not an expert in Jest mocking. referred How to mock useHistory hook in jest? and mocked useHistory.push. But the mock function is not being hit. I would appreciate any suggestions
const ReviseAction = ({
plans,
template,
coveragePercentage,
territoryName,
existingTemplateId,
}) => {
const history = useHistory();
const handleRevise = () => {
history.push({
pathname: "/xxx",
state: {
plans: plans,
template: template,
coveragePercentage: coveragePercentage,
territoryName: territoryName,
existingTemplateId: existingTemplateId,
},
});
};
return (
<button
data-testid="revise-button"
onClick={handleRevise}
key="revise-button"
>
Revise
</button>
);
};
Here is my test:
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import ReviseAction from "./ReviseAction";
import { HashRouter as Router } from "react-router-dom";
describe("ReviseAction", () => {
jest.mock("react-router-dom");
const pushMock = jest.fn();
//reactRouterDom.useHistory = jest.fn().mockReturnValue({push: pushMock});
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useHistory: () => ({
push: jest.fn()
})
}));
it("Renders component", async () => {
render(
<Router>
<ReviseAction
plans={[]}
template={{}}
coveragePercentage={"12"}
territoryName={"Name"}
existingTemplateId={"1234"}
/>
</Router>
);
fireEvent.click(screen.queryByTestId("revise-button"));
expect(pushMock).toHaveBeenCalled();
});
});
and getting
Expected number of calls: >= 1
Received number of calls: 0

This fixed it.
What I did:
Changed HashRouter import to router
passed the mock history as props to router.
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import ReviseAction from "./ReviseAction";
import { Router } from "react-router-dom";
describe("ReviseAction", () => {
const mockPush = jest.fn();
jest.mock("react-router-dom", () => ({
useHistory: () => ({
push: mockPush,
}),
}));
const mockHistory = { push: mockPush, location: {}, listen: jest.fn() };
it("Renders component", async () => {
render(
<Router history={mockHistory}>
<ReviseAction
plans={[]}
template={{}}
coveragePercentage={"12"}
territoryName={"Name"}
existingTemplateId={"1234"}
/>
</Router>
);
fireEvent.click(screen.queryByTestId("revise-button"));
expect(mockPush).toHaveBeenCalled();
});
});

Related

Jest mock variables of imported component

I am trying to mock a variable (auth) inside my App component as it is doing conditional rendering. How should I do it without trying to export the variable itself? Been trying for a few days with various solutions but I can't seem to cover it, and now I am stuck.
App.js
import React from "react";
import { useRoutes } from "react-router-dom";
import Routing from "./routes";
import useAuth from "./hooks/useAuth";
import SplashScreen from "./components/splashScreen/SplashScreen";
const App = () => {
const content = useRoutes(Routing());
const auth = useAuth();
return (
<>
{auth.isInitialized ? content : <SplashScreen />}
</>
);
};
export default App;
App.test.js
import React from "react";
import { mount } from "enzyme";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
describe("App Unit Tests", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<BrowserRouter>
<App />
</BrowserRouter>
);
});
it("App should render", () => {
expect(wrapper.length).toEqual(1);
});
//Below fails
it("should render splashscreen", () => {
jest.mock("./hooks/useAuth", () => ({
isInitialized: false,
}));
expect(wrapper.length).toEqual(1);
});
it("should render content", () => {
jest.mock("./hooks/useAuth", () => ({
isInitialized: true,
}));
expect(wrapper.length).toEqual(1);
});
});
You could do something like this:
jest.mock('./hooks/use-auth', () => ({
isInitialized: true
});
This basically means that use-auth returns an object which has a inInitialized property
Instead of auth, the useAuth hook should be mocked into an object (say mockUseAuth) that has the isInitialized getter. The getter should return a mockIsInitialized value, that can be changed on per test case basis. Something like this :
let mockIsInitialized = true;
let mockUseAuth = {
isAuthenticated: true
};
Object.defineProperty(mockUseAuth, 'isInitialized', {
get: jest.fn(() => mockIsInitialized)
});
jest.mock('./hooks/use-auth', () => {
return jest.fn(() => (mockUseAuth))
})
describe("App Unit Tests", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<BrowserRouter>
<App />
</BrowserRouter>
);
});
it("App should render", () => {
expect(wrapper.length).toEqual(1);
});
it("should render splashscreen", () => {
mockIsInitialized = false;
expect(wrapper.length).toEqual(1);
});
it("should render content", () => {
mockIsInitialized = true;
expect(wrapper.length).toEqual(1);
});
});

Mocking useHistory and expect toBeCalledWith

I wan't to check if history.push() has been called with the correct parameters in my test.
I'm not sure what's the correct way to mock useHistory()
I tried this solution. But it seems that I can't check if push() has been called.
App.tsx
import React from 'react';
import {useHistory} from 'react-router-dom';
const App: React.FC = () => {
const history = useHistory();
const onClick = () => {
history.push('/anotherPath');
};
return (
<div>
<button onClick={onClick}>click</button>
</div>
);
};
export default App;
App.test.tsx
import React from 'react';
import {render, fireEvent} from '#testing-library/react';
import App from './App';
import {useHistory} from 'react-router-dom'
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn(),
}),
}));
test('renders learn react link', async () => {
const app = render(<App/>);
fireEvent.click(app.getByText('click'));
expect(useHistory().push).toBeCalledWith('/anotherPath');
});
Is there any way to make sure that history.push() has been called with the correct parameters?
Try this, assign the mocked push method into a variable and use that to assert if it is called with the right parameters.
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import { useHistory } from "react-router-dom";
const mockHistoryPush = jest.fn();
const App: React.FC = () => {
const history = useHistory();
const onClick = () => {
history.push("/anotherPath");
};
return (
<div>
<button onClick={onClick}>click</button>
</div>
);
};
jest.mock("react-router-dom", () => ({
useHistory: () => ({
push: mockHistoryPush
})
}));
test("renders learn react link", async () => {
const { getByText } = render(<App />);
fireEvent.click(getByText("click"));
expect(mockHistoryPush).toBeCalledWith("/anotherPath");
});

Testing a React with React-Router v.5, useHistory, useSelector and useEffect

I struggle with writing a proper test for a component that protects some routes and programatically redirects unauthorized users. The component looks like this:
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { isAuthed } from 'redux/selectors/auth';
const mapState = state => ({
isAuthenticated: isAuthed(state),
});
const LoginShield = ({ children }) => {
const { isAuthenticated } = useSelector(mapState);
const history = useHistory();
useEffect(() => {
if (!isAuthenticated) {
history.push('/login');
}
}, [isAuthenticated]);
return children;
};
export default LoginShield;
I basically would like to check that the component redirects unauthenticated user and doesn't redirect an authenticated user (two basic test cases). I tried several approaches using Jest/Enzyme or Jest/ReactTestingLibrary and cannot find a good solution.
For now my test is a mess but I will share it so that someone can show me where the problem lays:
import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import rootReducer from 'redux/reducers';
import LoginShield from 'components/LoginShield/LoginShield';
describe('LoginShield component', () => {
let wrapper;
let historyMock;
beforeEach(() => {
const initialState = { auth: { loginId: 'Foo' } };
const store = createStore(rootReducer, initialState);
historyMock = {
push: jest.fn(),
location: {},
listen: jest.fn(),
};
jest.mock('react-redux', () => ({
useSelector: jest.fn(fn => fn()),
}));
wrapper = mount(
<Provider store={store}>
<Router history={historyMock}>
<LoginShield>
<h5>Hello Component</h5>
</LoginShield>
</Router>
</Provider>,
);
});
it('renders its children', () => {
expect(wrapper.find('h5').text()).toEqual('Hello Component');
});
it('redirects to the login page if user is not authenticated', async () => {
await act(async () => {
await Promise.resolve(wrapper);
await new Promise(resolve => setImmediate(resolve));
wrapper.update();
});
// is the above necessary?
console.log(historyMock.push.mock.calls);
// returns empty array
// ... ?
});
it('doesn`t redirect authenticated users', () => {
// .... ?
});
});
Any tips are more than welcome! Thank you in advance. :)

Mocking react-router-dom hooks using jest is not working

I'm using Enzyme's shallow method to test a component which uses the useParams hook to get an ID from the URL params.
I'm trying to mock the useParams hook so that it does't call the actual method, but it doesn't work. I'm still getting TypeError: Cannot read property 'match' of undefined, so it calls the actual useParams, and not my mock.
My component:
import React from 'react';
import { useParams } from 'react-router-dom';
export default () => {
const { id } = useParams();
return <div>{id}</div>;
};
Test:
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import Header from './header';
import { shallow } from 'enzyme';
Enzyme.configure({ adapter: new Adapter() });
describe('<Header />', () => {
jest.mock('react-router-dom', () => ({
useParams: jest.fn().mockReturnValue({ id: '123' }),
}));
it('renders', () => {
const wrapper = shallow(<Header />);
expect(wrapper).toBeTruthy();
});
});
Thank you!
This works for me to mock useParams and change values for each unit test within the same file:
import React from "react";
import { render } from "#testing-library/react";
import Router from "react-router-dom";
import Component from "./Component";
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useParams: jest.fn(),
}));
const createWrapper = () => {
return render(<Cases />);
};
describe("Component Page", () => {
describe("Rendering", () => {
it("should render cases container", () => {
jest.spyOn(Router, 'useParams').mockReturnValue({ id: '1234' })
const wrapper = createWrapper();
expect(wrapper).toMatchSnapshot();
});
it("should render details container", () => {
jest.spyOn(Router, 'useParams').mockReturnValue({ id: '5678' })
const wrapper = createWrapper();
expect(wrapper).toMatchSnapshot();
});
});
});
Just declare useParams as jest.fn() outside describe() and then change its values in each unit test with jest.spyOn
I am not sure why, also couldn't find it in the docs of react-router library, but changing react-router-dom to react-router in both tests and implementation worked for me.
So it becomes something like this:
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import Header from './header';
import { shallow } from 'enzyme';
Enzyme.configure({ adapter: new Adapter() });
describe('<Header />', () => {
jest.mock('react-router', () => ({
useParams: jest.fn().mockReturnValue({ id: '123' }),
}));
it('renders', () => {
const wrapper = shallow(<Header />);
expect(wrapper).toBeTruthy();
});
});
I've had a similar problem, I solved it like this:
import { Route, Router } from "react-router-dom";
import { createMemoryHistory } from "history";
const renderWithRouter = (component) => {
const history = createMemoryHistory({
initialEntries: ["/part1/idValue1/part2/idValue2/part3"],
});
const Wrapper = ({ children }) => (
<Router history={history}>
<Route path="/part1/:id1/part2/:id2/part3">{children}</Route>
</Router>
);
return {
...render(component, { wrapper: Wrapper }),
history,
};
};
describe("test", () => {
it("test desc", async () => {
const { getByText } = renderWithRouter(<MyComponent/>);
expect(getByText("idValue1")).toBeTruthy();
});
});
I tried this mock but it doesn't work to me. Error: Cannot read property 'match' of undefined. It seems the component is not inside a router so it cannot mock the match with params. It works to me:
import { MemoryRouter, Route } from 'react-router-dom';
const RenderWithRouter = ({ children }) => (
<MemoryRouter initialEntries={['uri/Ineed']}>
<Route path="route/Ineed/:paramId">{children}</Route>
</MemoryRouter>
);
const tf = new TestFramework();
describe('<MyComponent />', () => {
tf.init({ title: 'Some test' }, props =>
shallow(
<RenderWithRouter>
<MyComponent {...props} />
</RenderWithRouter>
)
);
it('Some description', () => {
const wrapper = tf.render().html();
expect(wrapper).toContain('something');
});
});
For me mocking react-router-dom fix the issue:
jest.mock('react-router-dom', () => ({
useParams: jest.fn().mockReturnValue({ nifUuid: 'nif123' }),
useHistory: jest.fn()
}));
I had the same issue. I mocked useParams like this:
jest.mock('react-router-dom', () => {
return {
useParams: () => ({
id: '123'
})
}
})
You might be missing to add other keys of react-router-dom as is.
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: jest.fn().mockReturnValue({ id: '123' })
}));
I had the same issue.
Calling the "cleanup" function from the "#testing-library/react" helps me:
import { cleanup } from '#testing-library/react';
afterEach(() => {
cleanup();
});

How to mock useHistory hook in jest?

I am using UseHistory hook in react router v5.1.2 with typescript? When running unit test, I have got issue.
TypeError: Cannot read property 'history' of undefined.
import { mount } from 'enzyme';
import React from 'react';
import {Action} from 'history';
import * as router from 'react-router';
import { QuestionContainer } from './QuestionsContainer';
describe('My questions container', () => {
beforeEach(() => {
const historyHistory= {
replace: jest.fn(),
length: 0,
location: {
pathname: '',
search: '',
state: '',
hash: ''
},
action: 'REPLACE' as Action,
push: jest.fn(),
go: jest.fn(),
goBack: jest.fn(),
goForward: jest.fn(),
block: jest.fn(),
listen: jest.fn(),
createHref: jest.fn()
};//fake object
jest.spyOn(router, 'useHistory').mockImplementation(() =>historyHistory);// try to mock hook
});
test('should match with snapshot', () => {
const tree = mount(<QuestionContainer />);
expect(tree).toMatchSnapshot();
});
});
Also i have tried use jest.mock('react-router', () =>({ useHistory: jest.fn() })); but it still does not work.
I needed the same when shallowing a react functional component that uses useHistory.
Solved with the following mock in my test file:
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn(),
}),
}));
This one worked for me:
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: jest.fn()
})
}));
Here's a more verbose example, taken from working test code (since I had difficulty implementing the code above):
Component.js
import { useHistory } from 'react-router-dom';
...
const Component = () => {
...
const history = useHistory();
...
return (
<>
<a className="selector" onClick={() => history.push('/whatever')}>Click me</a>
...
</>
)
});
Component.test.js
import { Router } from 'react-router-dom';
import { act } from '#testing-library/react-hooks';
import { mount } from 'enzyme';
import Component from './Component';
it('...', () => {
const historyMock = { push: jest.fn(), location: {}, listen: jest.fn() };
...
const wrapper = mount(
<Router history={historyMock}>
<Component isLoading={false} />
</Router>,
).find('.selector').at(1);
const { onClick } = wrapper.props();
act(() => {
onClick();
});
expect(historyMock.push.mock.calls[0][0]).toEqual('/whatever');
});
Wearing my politician hat I'll dare to state that you're asking the wrong question.
It's not useHistory that you want to mock. Instead you'd just want to feed it with history object which you control.
This also allows you to check for push invocations, just like the 2 top answers (as of writing this).
If that's indeed the case, createMemoryHistory got your back:
import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
test('QuestionContainer should handle navigation', () => {
const history = createMemoryHistory()
const pushSpy = jest.spyOn(history, 'push') // or 'replace', 'goBack', etc.
render(
<Router history={history}>
<QuestionContainer/>
</Router>
)
userEvent.click(screen.getByRole('button')) // or whatever action relevant to your UI
expect(pushSpy).toHaveBeenCalled()
})
In the Github react-router repo I found that the useHistory hook uses a singleton context, and that you can use a MemoryRouter to provide that context in tests.
import { MemoryRouter } from 'react-router-dom';
const tree = mount(
<MemoryRouter>
// Add the element using history here.
</MemoryRouter>
);
A way to mock the push function of useHistory:
import reactRouterDom from 'react-router-dom';
jest.mock('react-router-dom');
const pushMock = jest.fn();
reactRouterDom.useHistory = jest.fn().mockReturnValue({push: pushMock});
Then, how to check if the function have been called:
expect(pushMock).toHaveBeenCalledTimes(1);
expect(pushMock).toHaveBeenCalledWith('something');
This works for me, I was having problems with useLocation too
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn()
}),
useLocation: jest.fn().mockReturnValue({
pathname: '/another-route',
search: '',
hash: '',
state: null,
key: '5nvxpbdafa'
})}))
I found the above answers very helpful. However I missed the ability to spy and actually test functionality. But simply naming the mock function first solved that for me.
const mockPush = jest.fn();
jest.mock('react-router-dom', () => ({
useHistory: () => {
const push = () => mockPush ();
return { push };
},
}));

Resources