Jest test open react-select menu - reactjs

I have a component that uses react-select. I want to test the correct props are being passed to it, and those are being displayed correctly. Is there anyway to force the menu to open in enzyme/jest?

You can force change the state of the internal StateManager component
const tree = mount(<MyComponent />);
tree.find('Select').find('StateManager').instance().setState({ menuIsOpen: true });
tree.update();
Alternatively, a better way is to check the props of the component without opening the Select menu. This let's you abstract out react-select better in your tests.
const tree = mount(<MyComponent />);
// Run tests against options prop of Select
// expect(tree.find('Select').props('options')).toHaveLength(10);

Related

React jest snapshot only returning empty DocumentFragment

I am using React and writing a Modal component like so:
const MyModal: FC<MyModalProps> = memo((props) => {
return (
<Modal isOpen={true} data-client-id={modal-client-id}>
...
</Modal>
);
});
I am trying to test this using testing-library and jest like so:
const { asFragment } = render(<MyModal {...myTestProps} />);
const renderFragment = asFragment();
expect(renderFragment).toMatchSnapshot();
However, when I check the snapshot I only see <DocumentFragment />. I can test whether the modal is there by a getByTestId(modal-client-id) and I can see that the modal is rendering and appearing when I run in Storybook with the exact same props. The snapshot also works and returns the inner components when I remove the surrounding Modal component. Is there a reason why Snapshot would only return the DocumentFragment and not the full snapshot? Would it just imply that within the unit test the component is not rendering?
Testing-Library render appends the passed in component to a parent div. This corresponds with the Container option of the render function. By default Testing-Library creates a div and appends to it. In the case of the modal, the modal is a separate pop up that was not being rendered as a child of the div, which explains why the fragment only rendered as:
<DocumentFragment>
<div />
</DocumentFragment>
To debug this, printing out the rendered screen gave the clue with: screen.debug(). This showed that the modal was being rendered outside of the container div, which is why other queries/gets were able to find components.
Alternatively, we can also override the baseElement option, which defaults to document.body if not specified. In my case because any modal rendered would correctly be rendered on top of a component, I instead did:
const result = render(<MyModal {...myTestProps} />);
const modalComponent = screen.getByTestId('modal-client-id');
expect(modalComponent).toMatchSnapshot();
This avoids messing around with attempting to specify the container or baseElement options since anything rendered will be rendered on top.

Not simulating 'change' using enzyme

I have a Reactjs component and it has one button and a date-range picker.
I want to simulate onclick and onchange events of button and picker respectively.
I'm able to simulate onclick of the button. but on change of datepicker is not working
I have tried this
headerComponent.find(`#prev_button`).at(1).simulate("click");
headerComponent.find(`#dropdown`).at(1).simulate("change", { value: "t" });
please see this sandbox click here for full code and test file
Based on Enzyme documentation you make a mistake on your second argument on simulate function.
To simulate changes on the input, you should change it like this :
headerComponent.find(`#dropdown`).at(1).simulate("change", { target: { value: "t" } });
Testing with enzyme is tricky. You should try not to test dependencies because you trust those are already tested. Having said that, you could shallow render instead of mounting and look for the RangePicker component in the shallow tree, get the handler you are passing in the onChange prop and call it manually, then check the callback prop you pass to your component is called with the expected value.
describe.only("test", () => {
it("should render", () => {
const callBackToSetDates = jest.fn();
const callBackToSetFilter = jest.fn();
const wrapper = shallow(
<Header
{...headerProps1}
callBackToSetDates={callBackToSetDates}
callBackToSetFilter={callBackToSetFilter}
/>
);
const rangePickerOnChange = wrapper.find("RangePicker").prop("onChange");
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("someValue");
});
});
the purpose is to test only the logic you add inside your component, i.e., you transform the value you get from the RangePicker to something else
<RangePicker
...
onChange={(value) => {
callBackToSetDates(`I'm transforming ${value}`);
}}
/>
and in your test
rangePickerOnChange("someValue");
expect(callBackToSetDates).toHaveBeenCalledWith("I'm transforming someValue");
you can see it working here https://codesandbox.io/s/cool-rosalind-uec6t?file=/src/tests/index.test.js
If you really want to keep testing what the actual user sees, you'll need to fire the events that the user does when using the component. In this case: you need to click the input, look for a date, click it, then click another date to completely fire the onChange event of the RangePicker component. You might look at how antd test it and copy the necessary jest configuration they have to mock some DOM APIs

Jest & react-testing-library - add test for conditionally rendered children

I have a component for which I have this test
test('Some Test', () => {
const { getByTestId } = render(<SomeComponent>Some String</SomeComponent>);
const componentNode = getByTestId('primary');
expect(componentNode).toEqual('Some String');
});
This component accepts an optional prop loading (boolean) which is by default false. When loading is true, a spinner is rendered instead of any children passed to this component.
I want to create a test (and probably change the existing one) for when I pass the loading prop (to test that the component changes state). How can I do it the best way?
You need to mock loading prop in test and to pass it to your component. I guess it is true or false. But since in your test code you do not forward any loading prop, I assume it is then undefined inside component.
In case of false value, you need to verify that those conditionally rendered elements are not rendered. In case when you want to test that some element is not rendered, React Testing Library offers API just for that. From the docs:
queryBy...: Returns the first matching node for a query, and return
null if no elements match. This is useful for asserting an element
that is not present.
So inside your test, you need something like: expect(queryBy*(...)).not.toBeInTheDocument();
And for case when you forward true as value for loading prop, you need to verify that your elements are rendered using the await findBy*() API. You can take a look for more in link provided above.
You need to pass loading flag value as a prop and then assert based on props passed.
Ex:
test('Some Test', () => {
render(<SomeComponent loading={true}>Loading</SomeComponent>);
const loading = screen.queryByText('Loading')
expect(loading).toBeInTheDocument();
});
test('Some Test', () => {
render(<SomeComponent loading={false}>Hello</SomeComponent>);
const hello = screen.queryByText('Hello')
expect(hello).toBeInTheDocument();
});
Also you can refers to https://kentcdodds.com/blog/common-mistakes-with-react-testing-library which suggest to use screen for querying.

Firing a click event on a child component

I have been trying to write a test that will ensure that when a marker is clicked (from leaflet) further details will be displayed to the user. The Marker component is a child of the Map component. To start I am just wanting to see if the onClick function is called once when the marker is clicked.
The Map component returns the following structure
return(
<LeafletMap>
<Marker data-testid='marker' onClick={someFunc}/>
<TileLayer/>
<Popup/>
</LeafletMap>
)
In my test I attempt to render the Map component and find the marker via a data-testid:
const handleParcelClick = jest.fn()
it('get parcel details upon clicking the marker', () => {
const {getByTestId}= render(<Map lat={someNumber} lng={someNumber} zoom={14} parcels={fakeParcels} activeParcel={fakeDetails} onParcelClick={handleParcelClick} />)
const marker = getByTestId('marker')
fireEvent.click(marker)
expect(handleParcelClick).toBeCalledTimes(1)
});
When attempting to run I get the following error:
at getElementError (node_modules/#testing-library/dom/dist/query-helpers.js:22:10)
at args (node_modules/#testing-library/dom/dist/query-helpers.js:76:13)
at getByTestId (node_modules/#testing-library/dom/dist/query-helpers.js:59:17)
at Object.<anonymous>.it (src/ParcelDetails.test.tsx:58:20)
I have attempted using enzyme as well with no success. The data-testid in the actual code is unique for each marker, called marker above for simplicity. Am I going about this wrong? Should I be testing the Marker separately from the Map component?
Update: I have attempted to use enzyme as a solution; however, I receive the following error when trying to simulate a click
TypeError: Cannot read property '__reactInternalInstance$k2volvgmsgj' of null
There does not seem to be a consistent solution for this error and I am confused as to why I am getting it. I have ensured that marker is the component I am wanting to click and that it is not null.
Here is my updated code:
it('Loads parcel details on click', ()=> {
const mockClick = jest.fn();
const component = mount(<Map lat={n1} lng={n2} zoom={14} parcels={fakeParcels} activeParcel={fakeDetails} onParcelClick={mockClick} />);
const marker = component.find(Marker).first();
marker.simulate('click');
expect(mockClick).toBeCalledTimes(1);
});
The easiest way to fire a click event on an element in jest is to first find the element using dom selector and then simulate click on it, like this:
let element = document.getElementById('your-element-id');
element.simulate('click');
Hope this helps!!
I was able to get the desired behaviour via enzyme. Although it is not the best solution - it will do for now. I know shallow rendering is not the best practice.
Here is a snippet of my solution using shallow from enzyme:
it('Loads parcel details on click', ()=> {
const onParcelClick = jest.fn();
const component = shallow(<Map lat={n1} lng={n2} zoom={14} parcels={mockParcels} activeParcel={mockDetails} onParcelClick={onParcelClick} />);
const marker = component.find(Marker).first();
marker.simulate('click');
expect(onParcelClick).toBeCalledTimes(1);
});

How to create an unit test for UncontrolledTooltip from reactstrap that does not handle state management directly?

I implemented simple UncontrolledTooltip from reactstrap. The doc (https://reactstrap.github.io/components/tooltips/) says
uncontrolled component can provide the functionality wanted without the need to manage/control the state of the component
If I want to implement an unit test (e.g. jest + enzyme) for testing its state as either open or close, how can I create a unit test without manually tinkering with state value? Is this possible to achieve it? It seems only possible with regular Tooltip component but I like to hear advice from seasoned engineers.
[Update]:
Upon request I include here tooltip and unit test I am trying to execute. At the moment, I want to simulate hover on the tooltip however mockHover.mock.calls.length returns as 0 which I interpret as mock function was not triggered.
Here is my Tooltip.
import React from 'react';
import { UncontrolledTooltip } from 'reactstrap';
export default class MyTooltip extends React.Component {
render() {
const { metaData, wg } = this.props;
return (
<div>
<UncontrolledTooltip placement="bottom" trigger={'hover'} target={wg}>
{metaData}
</UncontrolledTooltip>
</div>
);
}
}
Here is my unit test that use jest and enzyme:
describe('<MyTooltip />', () => {
it('Tooltip unit test', () => {
const mockHover = jest.fn();
const wrapper = shallow(<MyTooltip trigger={mockHover} />);
expect(wrapper.find(UncontrolledTooltip));
wrapper.find(UncontrolledTooltip).simulate('hover');
expect(mockHover.mock.calls.length).toEqual(1);
});
});
There are few important things to start from:
UncontrolledTooltip is part of 3rd party package so you won't test it explicitly.
Instead you better focus on testing your wrapper around UncontrolledTooltip.
simulate is nothing related to events browser's system. It's just a syntax sugar to do props().onHover(...). So if target component has such a prop - and it's a callback-function - it will be called. If there is no such a prop - it would be up to defaultProps what's going on. Anyway nothing like 'emulating mouse cursor over the element'.
shallow() will stop rendering at level of UncontrolledTooltip(its internals will not be rendered)
Keeping that in mind I see you able only:
your component finally renders UncontrolledTooltip with expected constant prop values
both metaData and wg props are passed down to UncontrolledTooltip
it('renders UncontrolledTooltips under the hood', () => {
const wg = '1';
const metaData = (<span>2</span>);
const wrapper = shallow(<MyTooltip wg={wg} metaData={metaData} />);
const innerTooltip = wrapper.find(UncontrolledTooltip);
/*
I don't validate `find(UncontrolledTooltip).toHaveLength(1)`
since assertion on `.find(..).props()` would throw exception otherwise
*/
expect(innerTooltip.props().placement).toEqual('bottom');
expect(innerTooltip.props().trigger).toEqual('hover');
expect(innerTooltip.props().wg).toEqual(wg);
expect(innerTooltip.props().metaData).toEqual(metaData);
});

Resources