react testing library toHaveBeenCalled 0 mocking a simple click prop - javascript

I try to mock a prop called actionClicked but my test failed, I have a simple component as below
const ButtonAction: React.FC = ({ actionClicked }) => {
const handleClick = (action) => () => actionClicked(action)
return (
<div
data-testid="some-action"
onClick={handleClick('something')}
>
<Icon />
</div>
)
}
this is my test, button.spec.tsx
import { render, screen } from '#testing-library/react'
import userEvent from '#testing-library/user-event'
import React from 'react'
import ButtonAction from './ButtonAction'
test('render ButtonAction', () => {
const actionClicked = jest.fn()
render(<ButtonAction actionClicked={actionClicked} />)
userEvent.click(screen.getByTestId('some-action'))
expect(actionClicked).toHaveBeenCalled() //failed here
})
I'm not sure why actionClicked is not been called.
Expected number of calls: >= 1
Received number of calls: 0
Any thoughts?

This behaviour occurs because userEvent.click returns a promise.
First you should turn your test to async and then await userEvent.click:
test("render ButtonAction", async () => {
const actionClicked = jest.fn();
render(<ButtonAction actionClicked={actionClicked} />);
await userEvent.click(screen.getByTestId("some-action"));
expect(actionClicked).toHaveBeenCalledTimes(1);
});

Related

Mock antd useBreakpoint hook

I want to test the modal component, but there is an error with defining the cancel button,
it renders only if it's not mobile.
isMobile is a variable that is a boolean value from hook - useBreakpoint (ant design library hook).
I don't know how to mock that value, or how to click that button.
Note: if I remove the isMobile check, the button clicks well:)
import React from 'react'
import {Grid, Typography} from 'antd'
import {Button} from '#/components/Button'
import {Modal} from '#/components/Modal'
import translations from './translations'
import {ConfirmationModalProps} from './props'
const {Text} = Typography
const {useBreakpoint} = Grid
export const ConfirmationModal = ({visible, onClose, children}: ConfirmationModalProps) => {
const screens = useBreakpoint()
const isMobile = screens.xs
return (
<Modal
title={translations().chargeConfirmation}
visible={visible}
onOk={onClose}
onCancel={onClose}
footer={[
!isMobile && (
<Button role={'cancel-button'} type={'ghost'} key={'cancel'} onClick={onClose}>
{ translations().cancel }
</Button>
),
<Button type={'primary'} key={'charge'} onClick={onClose}>
{ translations().confirm }
</Button>
]}
>
<Text>{translations().confirmationText(children)}</Text>
</Modal>
)
}
describe('ConfirmationModal', () => {
it('should should the children and close button', async () => {
const onClose = jest.fn()
jest.mock('antd/es/grid/hooks/useBreakpoint', () => ({
xs: false
}))
render(<ConfirmationModal onClose={onClose} visible={true}>100</ConfirmationModal>)
const child = screen.getByText('Are you sure you want to charge 100')
expect(child).toBeTruthy()
expect(screen.queryByTestId('cancel')).toBeDefined()
await waitFor(() => screen.queryByTestId('cancel'))
fireEvent.click(screen.queryByRole('cancel-button'))
expect(onClose).toHaveBeenCalledTimes(1)
})
})
Errors are:
Error: Unable to fire a "click" event - please provide a DOM element.
Unable to find an accessible element with the role "cancel-button"
Depending on queryByRole or getByRole selector.
What is wrong?
Let's take a look at the source code of the useBreakpoint hook.
import { useEffect, useRef } from 'react';
import useForceUpdate from '../../_util/hooks/useForceUpdate';
import type { ScreenMap } from '../../_util/responsiveObserve';
import ResponsiveObserve from '../../_util/responsiveObserve';
function useBreakpoint(refreshOnChange: boolean = true): ScreenMap {
const screensRef = useRef<ScreenMap>({});
const forceUpdate = useForceUpdate();
useEffect(() => {
const token = ResponsiveObserve.subscribe(supportScreens => {
screensRef.current = supportScreens;
if (refreshOnChange) {
forceUpdate();
}
});
return () => ResponsiveObserve.unsubscribe(token);
}, []);
return screensRef.current;
}
export default useBreakpoint;
It uses ResponsiveObserve.subscribe() to get the supportScreens, it calls ResponsiveObserve.register(), the .register() method use window.matchMedia() underly. jestjs use JSDOM(a DOM implementation) as its test environment, but JSDOM does not implement window.matchMedia() yet. So we need to mock it, see Mocking methods which are not implemented in JSDOM
E.g.
import { render } from '#testing-library/react';
import React from 'react';
import { Grid } from 'antd';
const { useBreakpoint } = Grid;
describe('72021761', () => {
test('should pass', () => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(
(query) =>
({
addListener: (cb: (e: { matches: boolean }) => void) => {
cb({ matches: query === '(max-width: 575px)' });
},
removeListener: jest.fn(),
matches: query === '(max-width: 575px)',
} as any)
),
});
let screensVar;
function Demo() {
const screens = useBreakpoint();
screensVar = screens;
return <div />;
}
render(<Demo />);
expect(screensVar).toEqual({
xs: true,
sm: false,
md: false,
lg: false,
xl: false,
xxl: false,
});
});
});

React Unit Testing Jest History Push

I got this Method that I need to Test. I tried to mock the History but I couldn't get it working. I just want to check if there is a Button and if I click on it there should handle the DrawerToggle and push back to Path.
imports...
export const SettingsSidebar = ({ listItems }) => {
const gobackPath = (history.location.state as any)?.gobackPath;
some methods...
return (
...some other Codes
<DrawerMenu>
<GoBackButtonLg onClick={() => history.push(gobackPath)}>Zurück zum Workspace</GoBackButtonLg>
<SidebarList>
<SidebarListItem
goBackButton
button
key={'goback'}
icon={<GoBackButtonSm icon={faChevronLeft} />}
onClick={() => handleMenuClick(gobackPath)}
>
Zurück zum Workspace
</SidebarListItem>
{listItems.map(res => (
<SidebarListItem
button
key={res.name}
icon={<FontAwesomeIcon icon={res.icon} />}
onClick={() => handleMenuClick(res.path)}
>
{res.name}
</SidebarListItem>
))}
</SidebarList>
</DrawerMenu>
and so on...
);
};
export default SettingsSidebar;
The Test:
imports...
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
useHistory: () => ({ push: mockHistoryPush }),
}));
describe('SettingsSidebar', () => {
const mockHandleDrawerToggle = jest.fn();
beforeAll(() => {
configure({ adapter: new Adapter() });
});
it('should have a button', () => {
const wrapper = shallow(<SettingsSidebar listItems/>);
const button = wrapper.find(GoBackButton);
button.props().onClick();
expect(mockHandleDrawerToggle).toHaveBeenCalledTimes(1);
});
});
The Failed Message
You can use goBack method from the history object returned from useHistory. Look below for an example of how this works, also instead of invoking {selector}.props().click() you have to simulate the click event with enzyme.
Example.jsx
import React from "react";
import { MemoryRouter, useHistory } from "react-router-dom";
export const ExampleComponent = () => {
const history = useHistory();
return (
<div>
<button
data-testid="btn-go-back"
onClick={() => history.goBack()}
></button>
<button
data-testid="btn-go-to-some-path"
onClick={() => history.push("/to-some-path")}
></button>
</div>
);
};
export default ExampleComponent;
Example.spec.jsx | Example.test.jsx
import React from 'react';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import ReactRouterDOM from 'react-router-dom';
import ExampleComponent from './Example';
configure({ adapter: new Adapter() });
const mockHistoryPush = jest.fn();
const mockHistoryGoBack = jest.fn();
jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
goBack: mockHistoryGoBack,
}),
}));
describe('Example', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('invokes the history go back function when the go back button is clicked by the user', () => {
const wrapper = shallow(<ExampleComponent />);
const btn = wrapper.find("[data-testid='btn-go-back']");
btn.simulate('click');
expect(mockHistoryGoBack).toBeCalled();
});
it('invokes the history push function when the go back button is clicked by the user', async () => {
const wrapper = shallow(<ExampleComponent />);
const btn = await wrapper.find('[data-testid="btn-go-to-some-path"]')
btn.simulate('click');
expect(mockHistoryPush).toBeCalledWith('/to-some-path');
});
});

React testing library how to use waitFor

I'm following a tutorial on React testing. The tutorial has a simple component like this, to show how to test asynchronous actions:
import React from 'react'
const TestAsync = () => {
const [counter, setCounter] = React.useState(0)
const delayCount = () => (
setTimeout(() => {
setCounter(counter + 1)
}, 500)
)
return (
<>
<h1 data-testid="counter">{ counter }</h1>
<button data-testid="button-up" onClick={delayCount}> Up</button>
<button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
</>
)
}
export default TestAsync
And the test file is like this:
import React from 'react';
import { render, cleanup, fireEvent, waitForElement } from '#testing-library/react';
import TestAsync from './TestAsync'
afterEach(cleanup);
it('increments counter after 0.5s', async () => {
const { getByTestId, getByText } = render(<TestAsync />);
fireEvent.click(getByTestId('button-up'))
const counter = await waitForElement(() => getByText('1'))
expect(counter).toHaveTextContent('1')
});
The terminal says waitForElement has been deprecated and to use waitFor instead.
How can I use waitFor in this test file?
If you're waiting for appearance, you can use it like this:
it('increments counter after 0.5s', async() => {
const { getByTestId, getByText } = render(<TestAsync />);
fireEvent.click(getByTestId('button-up'));
await waitFor(() => {
expect(getByText('1')).toBeInTheDocument();
});
});
Checking .toHaveTextContent('1') is a bit "weird" when you use getByText('1') to grab that element, so I replaced it with .toBeInTheDocument().
Would it be also possible to wrap the assertion using the act
function? Based on the docs I don't understand in which case to use
act and in which case to use waitFor.
The answer is yes. You could write this instead using act():
import { act } from "react-dom/test-utils";
it('increments counter after 0.5s', async() => {
const { getByTestId, getByText } = render(<TestAsync />);
// you wanna use act() when there is a render to happen in
// the DOM and some change will take place:
act(() => {
fireEvent.click(getByTestId('button-up'));
});
expect(getByText('1')).toBeInTheDocument();
});
Hope this helps.
Current best practice would be to use findByText in that case. This function is a wrapper around act, and will query for the specified element until some timeout is reached.
In your case, you can use it like this:
it('increments counter after 0.5s', async () => {
const { findByTestId, findByText } = render(<TestAsync />);
fireEvent.click(await findByTestId('button-up'))
const counter = await findByText('1')
});
You don't need to call expect on its value, if the element doesn't exist it will throw an exception
You can find more differences about the types of queries here

how to get check setState function callled or not in react js using hooks?

I am trying to test custom hook. I want to know is setState function fire or not.
here is my custom hook
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
return { state };
};
now I am trying to test this custom hook. I want to know is setState function fire or not .
I tried like this
import moxios from "moxios";
import React from "react";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import useTabData from "./useTabData";
describe("use tab data", () => {
beforeEach(() => {
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
describe("non-error response", () => {
// create mocks for callback arg
const data = [
{
name: "hello"
}
];
let mockSetCurrentGuess = jest.fn();
beforeEach(async () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: data
});
});
});
test("calls setState with data", async () => {
React.useState = jest.fn(() => ["", mockSetCurrentGuess]);
const { result, waitForNextUpdate } = renderHook(() => useTabData());
console.log(result);
//expect(mockSetCurrentGuess).toHaveBeenCalledWith(data);
});
});
});
You should not mock the React internals. This is incorrect. Either ways, this code has no effect in mocking due to closures. Even if it worked, no point in testing if you are mocking the real implementation, isn't it ? :)
I would recommend to try to get grasp of what react hook is doing in your code.
You have a state in your custom hook:
const [state, setState] = React.useState([]);
.
.
return [state]; //you are returning the state as ARRAY
#testing-library/react-hooks allows you to debug and get value of current outcome of hook.
const { result, waitForNextUpdate } = renderHook(() => useTabData());
const [foo] = result.current; // the array you returned in hook
expect(foo).toEqual('bar'); //example assertion
I would stop here and allow you to learn and debug.

Testing method inside functional component in React using Jest / Enzyme

Following is my React Functional Component which I am trying to test using jest / enzyme.
React Functional Component Code -
export const UserForm = props => {
const {labels, formFields, errorMessages} = props;
const [showModal, setShowModal] = React.useState(false);
const [newId, setNewId] = React.useState('');
const showModal = () => {
setShowModal(true);
}
const closeModal = () => {
setShowModal(false);
};
const handleSubmit = data => {
Post(url, data)
.then(resp => {
const userData = resp.data;
setNewId(() => userData.id);
showModal();
})
}
return (
<div className="user-form">
<UserForm
fields={formFields}
handleSubmit={handleSubmit}
labels={labels}
errors={errorMessages}
/>
{showModal && <Modal closeModal={closeModal}>
<div className="">
<h3>Your new id is - {newId}</h3>
<Button
type="button"
buttonLabel="Close"
handleClick={closeModal}
classes="btn btn-close"
/>
</div>
</Modal>}
</div>
)
};
Now I am trying to test showModal, closeModal and handleSubmit method, but my tests are failing. Let me know the correct way of testing React Hooks and methods inside functional component.
My test case -
import React from 'react';
import { UserForm } from '../index';
import { shallow } from 'enzyme';
describe('<UserForm />', () => {
let wrapper;
const labels = {
success: 'Success Message'
};
const formFields = [];
const errorMessages = {
labels: {
firstName: 'First Name Missing'
}
};
function renderShallow() {
wrapper = shallow(<UserForm
labels={labels}
formFields={formFields}
errorMessages={errorMessages}
/>);
}
it('should render with props(snapshot)', () => {
renderShallow();
expect(wrapper).toMatchSnapshot();
});
it('should test showModal method', () => {
const mockSetShowModal = jest.fn();
React.useState = jest.fn(() => [false, mockSetShowModal]);
renderShallow();
expect(mockSetShowModal).toHaveBeenCalledWith(true);
});
});
Error I am getting -
Expected mock function to have been called with:
[true]
But it was not called.
Let me know how can i test the showModal, closeModal and handleSubmit methods in a functional component.
Generally, functional components in React aren't meant to be tested in that way. The React team are suggesting that you use the approach of React Testing Library which is more focused on the actual user interface scenarios. Instead of testing React component instances, you're testing DOM nodes.
This is the main reason why people are moving away from Enzyme and starting to use RTL, because you want to avoid testing implementation details as much as you can.

Categories

Resources