How to mock useHistory hook in jest? - reactjs

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

Related

Jest mock function not being called

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

Unit test TypeError: _reactRouterDom.useHistory.mockReturnValue is not a function

I try to write unit test with react component implement by typescript, but I got an error when mock some hook function.
My unit test implement:
import React from 'react';
import { useHistory } from 'react-router-dom';
import { MyComponent } from './index';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({ replace: jest.fn() ]),
}));
describe('MyComponent', () => {
it('Should pass', () => {
// My render implementation
(useHistory as jest.Mock).mockReturnValue({ pathname: '/test' });
});
});
When I run test case it throw an error:
TypeError: _reactRouterDom.useHistory.mockReturnValue is not a
function
You've assigned an anonymous function to useHistory instead of the mock function created by jest.fn(). .mockReturnValue() method exists on jest.fn(). Try:
import { useHistory } from 'react-router-dom';
jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom') as object),
useHistory: jest.fn(),
}));
describe('MyComponent', () => {
test('should pass', () => {
(useHistory as jest.Mock).mockReturnValue({ location: { pathname: '/test' } });
const history = useHistory();
expect(history.location.pathname).toBe('/test');
});
});

How do you mock useLocation() pathname using shallow test enzyme Reactjs?

I have header component like below:
import { useLocation } from "react-router-dom";
const Header = () => {
let route = useLocation().pathname;
return route === "/user" ? <ComponentA /> : <ComponentB />;
}
How will you mock this useLocation() to get the path as user?
I cant simply call the Header component as below in my test file as I am getting an error:
TypeError: Cannot read property 'location' of undefined at useLocation
describe("<Header/>", () => {
it("call the header component", () => {
const wrapper = shallow(<Header />);
expect(wrapper.find(ComponentA)).toHaveLength(1);
});
});
I have tried looking similar to the link How to test components using new react router hooks? but it didnt work.
I have tried like below:
const wrapper = shallow(
<Provider store={store}>
<MemoryRouter initialEntries={['/abc']}>
<Switch>
<AppRouter />
</Switch>
</MemoryRouter>
</Provider>,
);
jestExpect(wrapper.find(AppRouter)
.dive()
.find(Route)
.filter({path: '/abc'})
.renderProp('render', { history: mockedHistory})
.find(ContainerABC)
).toHaveLength(1);
from the link Testing react-router with Shallow rendering but it didnt work.
Please let me know.
Thanks in advance.
I found that I can mock the React Router hooks like useLocation using the following pattern:
import React from "react"
import ExampleComponent from "./ExampleComponent"
import { shallow } from "enzyme"
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useLocation: () => ({
pathname: "localhost:3000/example/path"
})
}));
describe("<ExampleComponent />", () => {
it("should render ExampleComponent", () => {
shallow(<ExampleComponent/>);
});
});
If you have a call to useLocation in your ExampleComponent the above pattern should allow you to shallow render the component in an Enzyme / Jest test without error.
I've been struggling with this recently too...
I found this works quite nicely:
import React from "react"
import ExampleComponent from "./ExampleComponent"
import { shallow } from "enzyme"
const mockUseLocationValue = {
pathname: "/testroute",
search: '',
hash: '',
state: null
}
jest.mock('react-router', () => ({
...jest.requireActual("react-router") as {},
useLocation: jest.fn().mockImplementation(() => {
return mockUseLocationValue;
})
}));
describe("<ExampleComponent />", () => {
it("should render ExampleComponent", () => {
mockUseLocationValue.pathname = "test specific path";
shallow(<ExampleComponent/>);
...
expect(...
});
});
this way, I was able to both mock useLocation and provide a value for pathname in specific tests as necessary.
HTH
If you are using react-testing-library:
import React from 'react';
import { Router } from 'react-router-dom';
import { render } from '#testing-library/react';
import { createMemoryHistory } from 'history';
import Component from '../Component.jsx';
test('<Component> renders without crashing', () => {
const history = createMemoryHistory();
render(
<Router history={history}>
<Component />
</Router>
);
});
More info: https://testing-library.com/docs/example-react-router/
I know this isn’t a direct answer to your question, but if what you want is to test the browser location or history, you can use mount and add an extra Route at the end where you can “capture” the history and location objects.
test(`Foobar`, () => {
let testHistory
let testLocation
const wrapper = mount(
<MemoryRouter initialEntries={[`/`]}>
<MyRoutes />
<Route
path={`*`}
render={routeProps => {
testHistory = routeProps.history
testLocation = routeProps.location
return null
}}/>
</MemoryRouter>
)
// Manipulate wrapper
expect(testHistory)...
expect(testLocation)...
)}
Have you tried:
describe("<Header/>", () => {
it("call the header component", () => {
const wrapper = shallow(<MemoryRouter initialEntries={['/abc']}><Header /></MemoryRouter>);
expect(wrapper.find(Header).dive().find(ComponentA)).toHaveLength(1);
});
});
When you use shallow only the first lvl is rendered, so you need to use dive to render another component.
None of the solutions above worked for my use case(unit testing a custom hook). I had to override the inner properties of useLocation which was read-only.
\\ foo.ts
export const useFoo = () => {
const {pathname} = useLocation();
\\ other logic
return ({
\\ returns whatever thing here
});
}
/*----------------------------------*/
\\ foo.test.ts
\\ other imports here
import * as ReactRouter from 'react-router';
Object.defineProperty(ReactRouter, 'useLocation', {
value: jest.fn(),
configurable: true,
writable: true,
});
describe("useFoo", () => {
it(' should do stgh that involves calling useLocation', () => {
const mockLocation = {
pathname: '/path',
state: {},
key: '',
search: '',
hash: ''
};
const useLocationSpy = jest.spyOn(ReactRouter, 'useLocation').mockReturnValue(mockLocation)
const {result} = renderHook(() => useFoo());
expect(useLocationSpy).toHaveBeenCalled();
});
});

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

Resources