Unit test for component in react - reactjs

hi i am new to react testing and i am using react testing library and jest.please help me in writing test case for below code.
import React from 'react'
import {
Loader,
SectionTitle,
ErrorLabel,
} from 'LookingGlass/common/components'
import LookingGlassPanelResultSection from 'LookingGlass/app/components/LookingGlassResultSection'
import constants from 'LookingGlass/constants'
import getErrorMessage from 'LookingGlass/app/utils/getErrorMessage'
import styles from './_.index.module.scss'
export default function LookingGlassPanelResults(props) {
const { queryResult, isTimedOut, error, isServerDown, isLoading } = props
if (isLoading)
return (
<Loader
content={constants.Loader.loaderContent}
active
inline="centered"
size="large"
/>
)
if (isServerDown) {
return <ErrorLabel text={constants.error.message.error_404} />
}
if (isTimedOut) {
return <ErrorLabel text={constants.requestsTimeout.msg} />
}
if (error) {
return <ErrorLabel text={getErrorMessage(error)} />
}
if (!queryResult) return null
return (
<div>
<hr className={styles.seperateLine} />
<SectionTitle text={constants.QueryPanelResult.resultPanel} />
<LookingGlassPanelResultSection queryResult={queryResult} />
</div>
)
}
ErrorLabel is different component where icon is used and text is displayed. How to write test where we use component inside component ?
this is my test case:-
const renderComponent = (props) =>
render(<LookingGlassPanelResults {...props} />)
test('Verify that isServerDown parameter works', () => {
const component = renderComponent({ isServerDown: true })
const { getByText } = within(component)
expect(
getByText('Service unavailable. Please try later'),
).toBeInTheDocument()
})
Error:-`
TypeError: Expected container to be an Element, a Document or a DocumentFragment but got Object.
20 | const { getByText } = within(component)
21 | expect(
> 22 | getByText('Service unavailable. Please try later'),
| ^
23 | ).toBeInTheDocument()
24 | })`

here first we need to get ErrorLabel by test-id and then check expectations
test('Verify that isServerDown parameter works', () => {
const component = renderComponent({ isServerDown: true })
const { getByTestId } = component
expect(getByTestId('ErrorLabel')).toHaveTextContent(
'Service unavailable. Please try later',
)
})

Related

react test component based on conditional component in parent component with react testing library

I'm trying to test component that is wrapped with other component, the parent component has conditional rendering, for explanation:
import React, { FunctionComponent } from 'react'
import { getPermissions, ROLES, PERMISSIONS } from '~/constants/permissions'
import usePermission from '~/hooks/usePermission'
export interface UsePermissionParams {
as: ROLES;
children?: React.ReactNode;
permissions: PERMISSIONS[];
}
// This Component is used to check if the user has the permission to access Specific Component or part of the UI
const CanAccess: FunctionComponent<UsePermissionParams> = ({
as,
children,
permissions,
}) => {
const hasPermission = usePermission({ permissions })
if (as === ROLES.VENDOR) {
const hasAllPermissions = permissions?.every((permission) =>
getPermissions.vendor.includes(permission)
)
if (hasPermission && hasAllPermissions) {
return <>{children}</>
}
} else if (as === ROLES.ADMIN) {
const hasAllPermissions = permissions?.every((permission) =>
getPermissions.admin.includes(permission)
)
if (hasPermission && hasAllPermissions) {
return <>{children}</>
}
}
return <></>
}
export default CanAccess
this is the parent component that wrap the component that I need to test.
and this is the component that I want to test:
import React, { useContext, useState } from 'react'
import { CircularProgress } from '#mui/material'
import { useSelector } from 'react-redux'
import CanAccess from '~/components/CanAccess'
import CreateBox from '~/components/CreateBox'
import { ROLES, PERMISSIONS } from '~/constants/permissions'
import CardHolder from '~/modules/finance/Components/SponsoredProductsTab/Components/CardHolder'
import TopUpModal from '~/modules/finance/Components/TopUpModal'
import { FinanceContext } from '~/modules/finance/FinanceContext'
import * as styles from '~/modules/finance/styles'
import { State } from '~/store'
import { User } from '~/store/user/types'
import { i18n } from '~/translations/i18n'
import { currencySign } from '~/utils/getCurrencySign'
import { ls } from '~/utils/localStorage'
import { separator } from '~/utils/numberSeperator'
const AccountBalance = () => {
const { list, loading } = useContext<any>(FinanceContext)
const [showTopUpModal, setShowTopUpModal] = useState<boolean>(false)
const userRepresentation = useSelector<State, User | null>(
(state) => state.user.data
)
const targetSelected = ls.get('target_code')
const handleOpenTopUpModal = () => {
setShowTopUpModal(true)
}
const onClose = () => {
setShowTopUpModal(false)
}
const renderCreateTopUP = () => (
<CanAccess as={ROLES.VENDOR} permissions={[PERMISSIONS.FO_TOPUP_ACCESS]}>
<CreateBox
label="Top Up"
maxWidth="fit-content"
handleCreateCampaign={handleOpenTopUpModal}
/>
</CanAccess>
)
return (
<>
{showTopUpModal && (
<TopUpModal
onClose={onClose}
advertiserId={
userRepresentation == null
? 0
: userRepresentation?.advertiserId || 0
}
target={targetSelected}
open
/>
)}
<CardHolder
cardHeader="Your account balance:"
cardContent={`${currencySign() || ''} ${
separator(list?.balance) || '0'
}`}
CardBottom={renderCreateTopUP()}
/>
</>
)
}
export default AccountBalance
and this is the test file:
import AccountBalance from '.'
import { render, screen } from '~/test-utils'
describe('AccountBalance', () => {
it('should render the top up button', async () => {
render(<AccountBalance />)
const topUpBtn = await screen.findByText('Top up')
expect(topUpBtn).toBeInTheDocument()
})
})
and I have this error:
<body>
<div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation1 css-s9smhs-MuiPaper-root"
>
<div>
<p
class="MuiTypography-root MuiTypography-body1 css-1tvvmmg-MuiTypography-root"
>
Your account balance:
</p>
<p
class="MuiTypography-root MuiTypography-body1 css-1hd1hip-MuiTypography-root"
>
0
</p>
<div
class="css-0"
/>
</div>
</div>
</div>
</body>
5 | it('should render the top up button', async () => {
6 | render(<AccountBalance />)
> 7 | const topUpBtn = await screen.findByText('Top up')
| ^
8 | expect(topUpBtn).toBeInTheDocument()
9 | })
10 | })
the problem is: any component that is wrapped with the component CanAccess is not rendering and I have null
so what is the problem

How to mock a module import with Sinon and ReactJS

I'm trying to write a unit test for one of my React components written in TS:
import React, { useContext } from 'react';
import Lottie from 'lottie-react-web';
import { ConfigContext } from '../ConfigProvider';
import type { UIKitFC } from '../../types/react-extensions';
// interfaces
export interface LoadingOverlayProps {
size: 'large' | 'medium' | 'small';
testId?: string;
}
interface LoaderProps {
size: 'large' | 'medium' | 'small';
}
const G3Loader: React.FC<LoaderProps> = ({ size }) => {
const options = { animationData };
const pxSize =
size === 'small' ? '100px' : size === 'medium' ? '200px' : '300px';
const height = pxSize,
width = pxSize;
return (
<div className="loader-container">
<Lottie options={options} height={height} width={width} />
<div className="loader__loading-txt">
<div>
<h4>Loading...</h4>
</div>
</div>
</div>
);
};
/**
* Description of Loading Overlay component
*/
export const LoadingOverlay: UIKitFC<LoadingOverlayProps> = (props) => {
const { testId } = props;
const { namespace } = useContext(ConfigContext);
const { baseClassName } = LoadingOverlay.constants;
const componentClassName = `${namespace}-${baseClassName}`;
const componentTestId = testId || `${namespace}-${baseClassName}`;
return (
<div id={componentTestId} className={componentClassName}>
<G3Loader size={props.size} />
</div>
);
};
LoadingOverlay.constants = {
baseClassName: 'loadingOverlay',
};
LoadingOverlay.defaultProps = {
testId: 'loadingOverlay',
};
export default LoadingOverlay;
The component uses an imported module "Lottie" for some animation, but I'm not interested in testing it, I just want to test my component and its props.
The problem is, when I run my unit test, I get an error:
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
After some research, I've concluded that the error is caused by the Lottie import so I would like to mock it for the purpose of my test. I'm using Mocha and Sinon's stub functionality to try and mock the library import, but the same error persists, making me feel like I'm not stubbing the module out correctly. Here's my latest attempt at a unit test:
import React from 'react';
import * as Lottie from 'lottie-react-web';
import { render } from '#testing-library/react';
import { expect } from 'chai';
import * as sinon from 'sinon';
import LoadingOverlay from '../src/components/LoadingOverlay';
const TEST_ID = 'the-test-id';
const FakeLottie: React.FC = (props) => {
return <div>{props}</div>;
};
describe('Loading Overlay', () => {
// beforeEach(function () {
// sinon.stub(Lottie, 'default').callsFake((props) => FakeLottie(props));
// });
console.log('11111');
it('should have a test ID', () => {
sinon.stub(Lottie, 'default').callsFake((props) => FakeLottie(props));
console.log(Lottie);
const { getByTestId, debug } = render(
<LoadingOverlay testId={TEST_ID} size="small" />
);
debug();
expect(getByTestId(TEST_ID)).to.not.equal(null);
});
});
I'm not really sure what else to try, unit tests are not my forte... If anyone can help, that would be great.
I answered my own questions... Posting in case somebody else runs into the same issue.
The error was complaining about HTMLCanvasElement. It turns out the component I was trying to stub out was using the Canvas library itself which wasn't required when running in the browser, but since I was building a test, I just added the Canvas library to my package and the issue was solved. Full code below:
import React from 'react';
import { render, cleanup } from '#testing-library/react';
import { expect, assert } from 'chai';
import * as lottie from 'lottie-react-web';
import { createSandbox } from 'sinon';
import LoadingOverlay from '../src/components/LoadingOverlay';
// test ID
const TEST_ID = 'the-test-id';
// mocks
const sandbox = createSandbox();
const MockLottie = () => 'Mock Lottie';
describe('Loading Overlay', () => {
beforeEach(() => {
sandbox.stub(lottie, 'default').callsFake(MockLottie);
});
afterEach(() => {
sandbox.restore();
cleanup();
});
it('should have test ID', () => {
const { getByTestId } = render(
<LoadingOverlay testId={TEST_ID} size="medium" />
);
expect(getByTestId(TEST_ID)).to.not.equal(null);
});
});

How to properly test React Native Modals using Jest and Native Testing Library

I'm having a bit of a hard time understanding how to test my modal component. I'm using the react-native-modals package and #testing-library/react-native with Jest. My component is a modal that pops up when a GraphQL error is passed to it.
./ErrorMessage.js
import React from 'react';
import PropTypes from 'prop-types';
import { Dimensions, Text } from 'react-native';
import Modal, { ModalContent, ScaleAnimation } from 'react-native-modals';
import { theme } from '../styles/theme.styles';
const ModalError = ({ error, onClose }) => {
if (!error || !error.message) {
return (
<Modal visible={false}>
<Text />
</Modal>
);
}
return (
<Modal
visible
modalAnimation={
new ScaleAnimation({
initialValue: 0,
useNativeDriver: true,
})
}
onTouchOutside={onClose}
swipeDirection={['up', 'down', 'left', 'right']}
swipeThreshold={200}
onSwipeOut={onClose}
modalStyle={modalStyle}
overlayOpacity={0.7}
>
<ModalContent>
<Text testID="graphql-error">{error.message}</Text>
</ModalContent>
</Modal>
);
};
ModalError.defaultProps = {
error: {},
};
ModalError.propTypes = {
error: PropTypes.object,
onClose: PropTypes.func.isRequired,
};
export default ModalError;
const window = Dimensions.get('window');
const modalStyle = {
backgroundColor: theme.lightRed,
borderLeftWidth: 5,
borderLeftColor: theme.red,
width: window.width / 1.12,
};
My test is pretty simple so far. I just want to make sure it's rendering the modal. I'm not exactly sure what needs to be mocked out here or how to do it.
./__tests__/ErrorMessage.test.js
import React from 'react';
import { MockedProvider } from '#apollo/react-testing';
import { GraphQLError } from 'graphql';
import { render } from '#testing-library/react-native';
import Error from '../ErrorMessage';
jest.mock('react-native-modals', () => 'react-native-modals');
const error = new GraphQLError('This is a test error message.');
const handleOnCloseError = jest.fn();
describe('<ErrorMessage>', () => {
it('should render an ErrorMessage modal component', () => {
const { container } = render(
<MockedProvider>
<Error error={error} onClose={handleOnCloseError} />
</MockedProvider>
);
expect(container).toMatchSnapshot();
});
});
The error that I'm getting is...
TypeError: _reactNativeModals.ScaleAnimation is not a constructor
18 | visible
19 | modalAnimation={
> 20 | new ScaleAnimation({
| ^
21 | initialValue: 0,
22 | useNativeDriver: true,
23 | })
And the snapshot is only printing...
./__tests__/__snapshots__/ErrorMessage.test.js.snap
// Jest Snapshot v1,
exports[`<ErrorMessage> should render an ErrorMessage modal component 1`] = `
<View
collapsable={true}
pointerEvents="box-none"
style={
Object {
"flex": 1,
}
}
/>
`;
How can I get past this error and make a proper snapshot?
you can use this -> https://github.com/testing-library/jest-native
In react native component,
...
<Modal
testID="test-modal"
deviceWidth={deviceWidth}
deviceHeight={deviceHeight}
isVisible={isModalVisible}. // isModalVisible = useState(true or false)
onBackdropPress={toggleModal}
backdropOpacity={0.5}
>
...
In test component,
...
const test = getByTestId("test-modal");
expect(test).toHaveProp("visible", true); // test success !
...
// components/Example/index.tsx
import React, { useState } from 'react';
import { Pressable, Text } from 'react-native';
import Modal from 'react-native-modal';
const Example: React.FC = () => {
const [isPrivacyPolicyVisible, setIsPrivacyPolicyVisible] = useState(false);
return (
<>
<Pressable onPress={() => setIsPrivacyPolicyVisible(true)}>
<Text>Privacy Policy</Text>
</Pressable>
<Modal
accessibilityLabel="privacy-policy-modal"
isVisible={isPrivacyPolicyVisible}>
<Text>Content</Text>
</Modal>
</>
);
};
export default Example;
// components/Example/index.test.tsx
import React from 'react';
import { fireEvent, render, waitFor } from '#testing-library/react-native';
import { Example } from 'components';
describe('Example Component', () => {
it('should render privacy policy.', async () => {
// Arrange
const { queryByText, queryByA11yLabel } = render(<Example />);
const button = queryByText(/privacy policy/i);
const modal = queryByA11yLabel('privacy-policy-modal');
// Act and Assert
expect(button).toBeTruthy();
expect(modal).toBeTruthy();
expect(modal.props).toMatchObject({
visible: false,
});
});
it('should show privacy policy modal.', async () => {
// Arrange
const { queryByText, queryByA11yLabel } = render(<Example />);
const button = queryByText(/privacy policy/i);
const modal = queryByA11yLabel('privacy-policy-modal');
// Act
await waitFor(() => {
fireEvent.press(button);
});
// Assert
expect(modal.props).toMatchObject({
visible: true,
});
});
});
when you do jest.mock('react-native-modals', () => 'react-native-modals'); you're replacing the whole library with the string 'react-native-modals' thus when you use it in your component it fails. You need to return a full mocked implementation from your mock function (second argument to jest.mock). It's also possible auto-mocking may work for you which would be done by simply doing: jest.mock('react-native-modals');
Here's the docks for jest.mock() with some examples of the various ways to use it: https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options.

React Router: Test a component that programatically pushes to react-router history on form submit

New to react-router and having issues with Jest/Enzyme tests failing due to react-routers history prop.
The idea:
On submit of the form component call handleSubmit.
handleSubmit calls validate to do some simple validation on the form.
If validated history.push('/new-url-to-navigate-to')
Else if not validated pushes error message to state.
The idea works when running the project - a valid form submit navigates to the /sent component, as expected.
So far so good.
The Issue
My issue comes from my jest/enzyme unit test to test whether the component renders an error message when the validation fails. The test code:
import React from 'react'
import { mount } from 'enzyme'
import { MemoryRouter } from 'react-router-dom'
import Form from '../Form'
describe('<Form />', () => {
it('validates invalid form', () => {
const component = mount(
<MemoryRouter
initialEntries={["/", "/form", "/saved"]}
initialIndex={1}
>
<Form />
</MemoryRouter>
)
const input = component.find('input')
input.value = ""
const form = component.find('form')
form.simulate('submit', { preventDefault: () => {},
target: {
routingTextbox: input
}});
expect(component.find('h3').length).toBe(1)
})
})
Which fails with:
<Form /> › validates invalid form
TypeError: Cannot read property 'push' of undefined
48 |
49 | if (isValid) {
> 50 | history.push('/sent')
The error prevents the test from running and fails.
If I remove/comment the history.push('/sent') line, the test passes, but I obviously need this line to navigate to the next page (as I say this works when running the project - it is just the test that is failing.
I seem to not have access to the history prop in my component.
Any help on how to move forward with this would be greatly appreciated.
Form component for reference
I don't think the issue is in here, since it's working perfectly when running the project, but would be happy to be proved wrong if I can get this test working!
// Form component
import React, { useState } from 'react';
const Form = ({ history }) => {
const [state, setState] = useState({
error: {
isValid: true,
message: ''
}
})
const handleClick = () => {
history.goBack()
}
const renderError = () => {
if (state.error.isValid === false) {
return(
<div>
<h3>{state.error.message}</h3>
</div>
)
}
return null
}
const validate = (value) => {
if (value === "") {
setState({
error: {
isValid: false,
message: 'Please enter a value in the textbox'
}
})
return false
}
return true
}
const handleSubmit = (e) => {
e.preventDefault()
const isValid = validate(e.target.routingTextbox.value)
if (isValid) {
history.push('/sent')
} else {
console.log('textbox is empty')
}
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="routingTextbox" />
<button type="submit">Submit</button>
<button type="button" onClick={handleClick}>Go Back</button>
{renderError()}
</form>
)
}
export default Form
Router for reference
const App = () => (
<Router>
<Switch>
<Route path="/" exact component={Index} />
<Route path="/form" component={Form} />
<Route path="/sent" component={Sent} />
</Switch>
</Router>
)
export default App;

React Native Testing Library doesn't find text even though its in debug

I'm building a React Native application with TypeScript. I'm using React Native Testing Library for my component tests.
I have a simple component that renders two clickable icons and a text. It's a counter that can increment and decrement the number.
import React, { PureComponent } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import { Button, Icon } from "react-native-elements";
import { getIconName } from "../../services/core";
import styles from "./styles";
export interface AmountButtonProps {
amount: number;
onDecrement: () => void;
onIncrement: () => void;
size: "small" | "large";
}
export class AmountButtons extends PureComponent<AmountButtonProps> {
render() {
const { amount, onDecrement, onIncrement, size } = this.props;
const fontSize = size === "small" ? 14 : 26;
const minusDisabled = amount <= 1;
const plusDisabled = amount >= 25;
return (
<View style={styles.container}>
<Icon
containerStyle={[
styles[size],
styles.iconContainer,
styles.minusIcon,
minusDisabled && styles.disabled
]}
onPress={onDecrement}
type="ionicon"
name={getIconName("remove")}
disabled={minusDisabled}
disabledStyle={[styles.iconDisabled, styles.disabled]}
size={fontSize}
component={TouchableOpacity}
/>
<View style={[styles[size], styles.amountContainer, styles.iconContainer]}>
<Text style={{ fontSize }}>{amount}</Text>
</View>
<Icon
containerStyle={[
styles[size],
styles.iconContainer,
styles.addIcon,
plusDisabled && styles.disabled
]}
onPress={onIncrement}
type="ionicon"
name={getIconName("add")}
disabled={plusDisabled}
disabledStyle={styles.iconDisabled}
color="white"
size={fontSize}
component={TouchableOpacity}
/>
</View>
);
}
}
export default AmountButtons;
I wanted to write a simple unit test to see if the user can see the amount. Here is what I wrote.
import React from "react";
import { debug, fireEvent, render } from "react-native-testing-library";
import { getIconName } from "../../services/core";
import AmountButtons, { AmountButtonProps } from "./AmountButtons";
const createTestProps = (props?: object): AmountButtonProps => ({
amount: 1,
onDecrement: jest.fn(),
onIncrement: jest.fn(),
size: "large",
...props
});
describe("AmountButtons", () => {
const props = createTestProps();
const { getByText, getByProps } = render(<AmountButtons {...props} />);
it("displays the amount", () => {
debug(<AmountButtons {...props} />);
expect(getByText(props.amount.toString())).toBeDefined();
});
});
The problem is this test throws the error:
● AmountButtons › displays the amount
Component not found.
18 | it("displays the amount", () => {
19 | debug(<AmountButtons {...props} />);
> 20 | expect(getByText(props.amount.toString())).toBeDefined();
| ^
21 | });
22 |
23 | it("calls onIncrement", () => {
at Object.it (app/components/AmountButtons/AmountButtons.test.tsx:20:12)
Even though in the output of debug I can see the amount being rendered:
...
}
>
<Text
style={
Object {
"fontSize": 26,
}
}
>
1
</Text>
</View>
<Themed.Icon
...
What is going on here? Why does React Testing Library not see this text? How can I test this?
The problem is that rendering your component with RTL's render method does not happen in sync with the test case. So when the it block runs you can't be sure that this line of code
const { getByText, getByProps } = render(<AmountButtons {...props} />);
has run and getByText is bound properly.
In order to solve this you can:
move rendering inside it block:
describe("AmountButtons", () => {
const props = createTestProps();
it("displays the amount", () => {
const { getByText, getByProps } = render(<AmountButtons {...props} />);
expect(getByText(props.amount.toString())).toBeDefined();
});
});
move rendering inside a beforeEach/before block:
describe("AmountButtons", () => {
const props = createTestProps();
let getByText, getByProps;
beforeEach(() => {
({ getByText, getByProps }) = render(<AmountButtons {...props} />);
})
it("displays the amount", () => {
expect(getByText(props.amount.toString())).toBeDefined();
});
});
but in this case you have to keep in let variables all the getBy helpers.

Resources