I have been trying to write tests for the following react component which returns different components depending on my props:
const Choice: React.FC<States> = props => {
function getChoiceComponent(): JSX.Element {
if (props.choices) {
return <FirstComponent {...props} />;
} else {
return <SecondComponent {...props} />;
}
}
return <>{getChoiceComponent()}</>;
};
How can I mock getChoiceComponent function and test it?
We should test the react component by changing the props and state rather than test the getChoiceComponent method directly. Here is the unit test solution,
index.tsx:
import React from 'react';
import FirstComponent from './first';
import SecondComponent from './second';
type States = any;
const Choice: React.FC<States> = (props) => {
function getChoiceComponent(): JSX.Element {
if (props.choices) {
return <FirstComponent {...props} />;
} else {
return <SecondComponent {...props} />;
}
}
return <>{getChoiceComponent()}</>;
};
export default Choice;
first.tsx:
import React from 'react';
const FirstComponent = () => <div>first component</div>;
export default FirstComponent;
second.tsx:
import React from 'react';
const SecondComponent = () => <div>second component</div>;
export default SecondComponent;
index.test.tsx:
import Choice from './';
import FirstComponent from './first';
import SecondComponent from './second';
import React from 'react';
import { shallow } from 'enzyme';
describe('60152774', () => {
it('should render first component', () => {
const props = { choices: [] };
const wrapper = shallow(<Choice {...props}></Choice>);
expect(wrapper.find(FirstComponent)).toBeTruthy();
});
it('should render second component', () => {
const props = {};
const wrapper = shallow(<Choice {...props}></Choice>);
expect(wrapper.find(SecondComponent)).toBeTruthy();
});
});
Unit test results with coverage report:
PASS stackoverflow/60152774/index.test.tsx
60152774
✓ should render first component (20ms)
✓ should render second component (5ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 88.24 | 100 | 50 | 100 |
first.tsx | 75 | 100 | 0 | 100 |
index.tsx | 100 | 100 | 100 | 100 |
second.tsx | 75 | 100 | 0 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.065s
Source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60152774
Related
Having the following component:
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useRouteMatch, Link } from 'react-router-dom';
interface MyComponentProps {
myId?: string;
link?: string;
}
export const MyComponent: React.FunctionComponent<MyComponentProps> = ({
myId = 'default-id',
link,
children
}) => {
const [myOutlet, setMyOutlet] = useState<HTMLOListElement>();
const match = useRouteMatch();
useEffect(() => {
const outletElement = document.getElementById(myId) as HTMLOListElement;
if (outletElement) {
setMyOutlet(outletElement);
}
}, [myId]);
if (!myOutlet) {
return null;
}
return createPortal(
<li>
<Link to={link || match.url}>{children}</Link>
</li>,
myOutlet
);
};
export default MyComponent;
I want to write unit tests using React Testing Library for it, the problem is that it keeps throwing an error because of useRouteMatch.
Here is my code:
import { render, screen } from '#testing-library/react';
import { MyComponent } from './my-component';
describe('MyComponent', () => {
const testId = 'default-id';
const link = '/route';
it('should render MyComponent successfully', () => {
const element = render(<MyComponent myId={testId} link={link} />);
expect(element).toBeTruthy();
});
});
The error appears at the line with const match = useRouteMatch();, is there a way to include this part in the test?
You should use <MemoryRouter>:
A <Router> that keeps the history of your “URL” in memory (does not read or write to the address bar)
Provide mock locations in the history stack by using the initialEntries props.
Then, use <Route> component to render some UI when its path matches the current URL.
The following example, assuming that the location pathname in the browser current history stack is /one, <Route>'s path prop is also /one, The two matching, rendering MyComponent.
E.g.
my-component.tsx:
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useRouteMatch, Link } from 'react-router-dom';
interface MyComponentProps {
myId?: string;
link?: string;
}
export const MyComponent: React.FunctionComponent<MyComponentProps> = ({ myId = 'default-id', link, children }) => {
const [myOutlet, setMyOutlet] = useState<HTMLOListElement>();
const match = useRouteMatch();
console.log('match: ', match);
useEffect(() => {
const outletElement = document.getElementById(myId) as HTMLOListElement;
if (outletElement) {
setMyOutlet(outletElement);
}
}, [myId]);
if (!myOutlet) {
return null;
}
return createPortal(
<li>
<Link to={link || match.url}>{children}</Link>
</li>,
myOutlet
);
};
export default MyComponent;
my-component.test.tsx:
import React from 'react';
import { render, screen } from '#testing-library/react';
import { MyComponent } from './my-component';
import { MemoryRouter, Route } from 'react-router-dom';
describe('MyComponent', () => {
const testId = 'default-id';
const link = '/route';
it('should render MyComponent successfully', () => {
const element = render(
<MemoryRouter initialEntries={[{ pathname: '/one' }]}>
<Route path="/one">
<MyComponent myId={testId} link={link} />
</Route>
</MemoryRouter>
);
expect(element).toBeTruthy();
});
});
test result:
PASS examples/70077434/my-component.test.tsx (8.433 s)
MyComponent
✓ should render MyComponent successfully (46 ms)
console.log
match: { path: '/one', url: '/one', isExact: true, params: {} }
at MyComponent (examples/70077434/my-component.tsx:13:11)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 87.5 | 28.57 | 100 | 86.67 |
my-component.tsx | 87.5 | 28.57 | 100 | 86.67 | 18,26
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.951 s, estimated 9 s
package versions:
"react": "^16.14.0",
"react-router-dom": "^5.2.0"
I'm new to testing with jest and I want to test the following code.
import React from "react";
import "./ButtonLogin.css";
import { Link } from 'react-router-dom';
function ButtonLogin() {
return (
<Link to="/login"> <button className="button-login">Iniciar sesión</button></Link>
)
}
export default ButtonLogin;
import { MemoryRouter } from 'react-router-dom';
import { render, fireEvent, Link } from '#testing-library/react';
import { ButtonLogin } from './ButtonLogin';
it('routes to a new route', async () => {
ButtonLogin = jest.fn();
const { getByText } = render(
<MemoryRouter ButtonLogin={ButtonLogin}>
<Link to="/login">Iniciar sesión</Link>
</MemoryRouter>
);
fireEvent.click(getByText('Iniciar sesión'));
expect(ButtonLogin).toHaveBeenCalledWith('/login');
});
I have performed the following test but it fails and I get the following error in line 9.
routes to a new route
"ButtonLogin" is read-only.
You can use the createMemoryHistory function and Router component to test it. Create a memory history with initial entries to simulate the current location, this way we don't rely on the real browser environment. After firing the click event, assert the pathname is changed correctly or not.
ButtonLogin.tsx:
import React from 'react';
import { Link } from 'react-router-dom';
function ButtonLogin() {
return (
<Link to="/login">
<button className="button-login">Iniciar sesión</button>
</Link>
);
}
export default ButtonLogin;
ButtonLogin.test.tsx:
import { fireEvent, render } from '#testing-library/react';
import React from 'react';
import { Router } from 'react-router-dom';
import ButtonLogin from './ButtonLogin';
import { createMemoryHistory } from 'history';
describe('ButtonLogin', () => {
test('should pass', () => {
const history = createMemoryHistory({ initialEntries: ['/home'] });
const { getByText } = render(
<Router history={history}>
<ButtonLogin />
</Router>
);
expect(history.location.pathname).toBe('/home');
fireEvent.click(getByText('Iniciar sesión'));
expect(history.location.pathname).toBe('/login');
});
});
test result:
PASS examples/69878146/ButtonLogin.test.tsx (10.675 s)
ButtonLogin
✓ should pass (41 ms)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
ButtonLogin.tsx | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.722 s, estimated 12 s
package version: "react-router-dom": "^5.2.0"
I'm using NextJS and I'm trying to test the ClientPortal component. I'm using Jest and React Testing Library for testing.
Here's the ClientPortal component:
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
export default function ClientPortal({ children, selector }) {
const ref = useRef();
const [mounted, setMounted] = useState(false);
useEffect(() => {
ref.current = document.querySelector(selector);
setMounted(true);
}, [selector]);
return mounted ? createPortal(children, ref.current) : null;
}
How can this be tested using Jest?
First, make sure the testEnvironment configuration is jsdom. For jestjs v26, it's jsdom by default. For jestjs v27, follow this guide to setup the testEnvironment configuration.
The test method is very straightforward. Create a DOM container to store the portal. Query the DOM element and assert whether it exists.
index.jsx:
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
export default function ClientPortal({ children, selector }) {
const ref = useRef();
const [mounted, setMounted] = useState(false);
useEffect(() => {
ref.current = document.querySelector(selector);
setMounted(true);
}, [selector]);
return mounted ? createPortal(children, ref.current) : null;
}
index.test.jsx:
import React from 'react';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import ClientPortal from './';
function TestChild() {
return <div>child</div>;
}
describe('69550058', () => {
test('should pass', () => {
const main = document.createElement('main');
const portalContainer = document.createElement('div');
portalContainer.id = 'portal-container';
document.body.appendChild(portalContainer);
const { container } = render(
<ClientPortal selector={'#portal-container'}>
<TestChild />
</ClientPortal>,
{ container: document.body.appendChild(main) }
);
expect(screen.getByText(/child/)).toBeInTheDocument();
expect(portalContainer.innerHTML).toEqual('<div>child</div>');
expect(container).toMatchInlineSnapshot(`<main />`);
});
});
test result:
PASS examples/69550058/index.test.jsx (8.941 s)
69550058
✓ should pass (33 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.jsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 passed, 1 total
Time: 9.624 s, estimated 11 s
package versions:
"jest": "^26.6.3",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"#testing-library/react": "^11.2.2",
I am a junior developer working on a React application using Jest as unit tests framework
I have to test my privateRoute file:
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => {
const currentUser = authenticationService.currentUser;
if (!currentUser) {
// not logged in so redirect to login page with the return url
return (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
);
}
// authorized so return component
return <Component {...props} />;
}}
/>
);
I am unable to test the condition if (!currentUser) { until the return
Do you happen to have any piece of advice on how to test this line?
I tried to mock authenticationService.currentUser using jest.fn but with no success
Here is the piece of code of authenticationService:
const currentUserSubject = new BehaviorSubject(
JSON.parse(localStorage.getItem("currentUser"))
);
export const authenticationService = {
// ...
currentUser: currentUserSubject.asObservable(),
// ...
};
Unit testing solution for PrivateRoute component using enzyme module.
privateRoute.tsx:
import React from 'react';
import { Route, Redirect } from 'react-router';
import { authenticationService } from './authenticationService';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
const currentUser = authenticationService.currentUser;
if (!currentUser) {
return <Redirect to={{ pathname: '/', state: { from: props.location } }} />;
}
return <Component {...props} />;
}}
/>
);
authenticationService.ts:
export const authenticationService = {
currentUser: {},
};
privateRoute.test.ts:
import React from 'react';
import { PrivateRoute } from './privateRoute';
import { mount, shallow } from 'enzyme';
import { MemoryRouter, Redirect, Router } from 'react-router';
import { authenticationService } from './authenticationService';
describe('59825407', () => {
it('should render component if current user exists', () => {
const mProps = { component: jest.fn().mockReturnValueOnce(null) };
const wrapper = mount(
<MemoryRouter>
<PrivateRoute {...mProps}></PrivateRoute>
</MemoryRouter>,
);
expect(wrapper.find(mProps.component).props()).toEqual(
expect.objectContaining({
history: expect.any(Object),
location: expect.any(Object),
match: expect.any(Object),
}),
);
});
it('should redirect if current user does not exist ', () => {
authenticationService.currentUser = undefined as any;
const mProps = { component: jest.fn().mockReturnValueOnce(null), path: '/user' };
const wrapper = mount(
<MemoryRouter initialEntries={['/user']}>
<PrivateRoute {...mProps}></PrivateRoute>
</MemoryRouter>,
);
const history = wrapper.find('Router').prop('history') as any;
expect(history.location.state.from.pathname).toBe('/user');
expect(history.location.pathname).toBe('/');
});
});
Unit test results with 100% coverage:
PASS src/stackoverflow/59825407/privateRoute.test.tsx (16.491s)
59825407
✓ should render component if current user exists (74ms)
✓ should redirect if current user does not exist (12ms)
--------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
authenticationService.ts | 100 | 100 | 100 | 100 | |
privateRoute.tsx | 100 | 100 | 100 | 100 | |
--------------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 18.683s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59825407
So I was trying to create a unit test using jest in ReactJS. The Unit test itself just to verify if the function (from action) has been called
I already tried to mock the function, but the result tells that I must mock the function
Here the code of the function that I want to create a unit test
import { testfunction } from '../../actions/auth';
handleSubmit(userParams) {
this.setState({ form: { ...this.state.form, isFetching: true } });
this.props.dispatch(testfunction(userParams,
this.successCallback.bind(this), this.errorCallback.bind(this)));
}
and for the unit test
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import Login from '../../../components/auth/Login';
const mockStore = configureStore([thunk]);
const initialState = {
history: { },
};
const store = mockStore(initialState);
let wrapper;
let history;
let testfunction;
beforeEach(() => {
testfunction= jest.fn();
history = { push: jest.fn() };
wrapper = shallow(
<Login
history={history}
store={store}
testfunction={testfunction}
/>
);
});
describe('handleSubmit()', () => {
test('should call testfunction props', () => {
const component = wrapper.dive();
const instance = component.instance();
const sampleUserParams = {
email: 'test#test.com',
password: 'samplePassword123',
};
instance.handleSubmit(sampleUserParams);
expect(testfunction).toHaveBeenCalled();
});
});
I just want to check if the "testfunction" is called when I called handleSubmit function. But the error message is:
"Expected mock function to have been called."
it feels my way to mock the function is wrong. Does anyone know how to correct way to test that function?
Here is the solution:
index.tsx:
import React, { Component } from 'react';
import { testfunction } from './testfunction';
class Login extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
form: {}
};
}
public render() {
const userParams = {};
return (
<div className="login">
<form onSubmit={() => this.handleSubmit(userParams)}>some form</form>
</div>
);
}
private handleSubmit(userParams) {
this.setState({ form: { ...this.state.form, isFetching: true } });
this.props.dispatch(testfunction(userParams, this.successCallback.bind(this), this.errorCallback.bind(this)));
}
private successCallback() {
console.log('successCallback');
}
private errorCallback() {
console.log('errorCallback');
}
}
export { Login };
testFunction.ts:
async function testfunction(userParams, successCallback, errorCallback) {
return {
type: 'ACTION_TYPE',
payload: {}
};
}
export { testfunction };
Unit test:
import React from 'react';
import { shallow } from 'enzyme';
import { Login } from './';
import { testfunction } from './testfunction';
jest.mock('./testfunction.ts');
describe('Login', () => {
const dispatch = jest.fn();
const sampleUserParams = {
email: 'test#test.com',
password: 'samplePassword123'
};
it('handleSubmit', () => {
const wrapper = shallow(<Login dispatch={dispatch} />);
expect(wrapper.is('.login')).toBeTruthy();
expect(wrapper.find('form')).toHaveLength(1);
wrapper.find('form').simulate('submit');
const cmpInstance = wrapper.instance();
expect(dispatch).toBeCalledWith(
// tslint:disable-next-line: no-string-literal
testfunction(sampleUserParams, cmpInstance['successCallback'], cmpInstance['errorCallback'])
);
// tslint:disable-next-line: no-string-literal
expect(testfunction).toBeCalledWith(sampleUserParams, cmpInstance['successCallback'], cmpInstance['errorCallback']);
});
});
Unit test with coverage report:
PASS src/stackoverflow/57847401/index.spec.tsx
Login
✓ handleSubmit (22ms)
-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 86.36 | 100 | 62.5 | 85 | |
index.tsx | 90 | 100 | 71.43 | 88.89 | 27,30 |
testfunction.ts | 50 | 100 | 0 | 50 | 2 |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.201s, estimated 4s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57847401