Jest mock variables of imported component - reactjs

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

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

testing-library/react rendering empty <div /> for a redux toolkit component

The issue it produces is it won't successfully render the MyComponent to the Mock DOM. console.log store displays the correct state in the store as well. But it's just rendering the empty in the body tag.
import React from 'react';
import configureMockStore from 'redux-mock-store';
import * as actions from 'store/reducer/reducer';
import { fireEvent, render, screen } from 'testUtils';
import MyComponent from 'components/MyComponent';
import { initialState } from 'store/reducer/reducer';
const mockStore = configureMockStore();
describe('my component', () => {
let message;
beforeAll(() => {
message = 'testing';
});
it('test 1', () => {
const store = mockStore({
myState: {
...initialState,
message,
},
});
render(<MyComponent />, {
store: store,
});
screen.debug();
expect(screen.queryAllByText(message).length).toBe(1);
});
});
// in testUtils
function render(ui, { store = configureStore(), ...renderOptions } = {}) {
function Wrapper({ children }) {
return (
// ..some other Providers
<Provider store={store}>
{children}
</Provider>
);
}
export {render};
now the screen.debug() only shows
<body>
<div />
</body>
// in MyComponent
const MyComponent = (): JSX.Element => {
const dispatch = useDispatch();
const myState = useSelector(myReducer);
return (
<AnotherComponent
isOpen={myState?.isOpen}
message={myState?.message}
/>
);
};
I found the reason why it's not working. It's because, in myComponent, I had a typo in the conditional statement. Thanks, everyone.

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