jest/enzyme mock function in functional component - reactjs

I have a functional component and I wanted to test it with mock function
(simplified demonstration)
const remove = () => {
... do something
}
const removeButton = (props) => (
<Button onClick={() => remove()}>
Remove
</Button>
);
I tried with this test case
it('test remove button', () => {
const test = shallow(<removeButton/>)
const mockFunction = jest.fn()
test.instance().remove = mockFunction
test.find('Button').simulate('click')
expect(mockFunction).toHaveBeenCalled()
})
.instance().remove could not mock the function because it is out of scope.
How would I mock the function remove ?

Here is a working example:
// ---- comp.js ----
import * as React from 'react';
import * as comp from './comp';
export const remove = () => {
// ...do something
}
export const RemoveButton = (props) => (
<div onClick={() => comp.remove()}>
Remove
</div>
);
// ---- comp.test.js ----
import * as React from 'react';
import { shallow } from 'enzyme';
import * as comp from './comp';
describe('removeButton', () => {
it('should call remove on click', () => {
const mock = jest.spyOn(comp, 'remove');
mock.mockImplementation(() => {});
const component = shallow(<comp.RemoveButton />);
component.find('div').simulate('click');
expect(mock).toHaveBeenCalled();
mock.mockRestore();
});
});
Note that to mock remove you need to export it and you need to import the module back into itself and use the import within your component.
Having said that, I agree that passing remove in as a prop is a better approach. It is much easier to test and makes your components more reusable.

You should pass the remove function as a prop, rather than just defining an adjacent variable that is private to a module.
const removeButton = (props) => (
<Button onClick={() => props.remove()}>
Remove
</Button>
)
// test file
it('test remove button', () => {
const mockFunction = jest.fn()
const test = shallow(<RemoveButton remove={mockFunction} />)
test.find('Button').simulate('click')
expect(mockFunction).toHaveBeenCalled()
})

Related

Test the methods inside a functional component using react

I have the following component and I am trying to test these two methods onHandleClick and onHandleSave for checking the visible states but I am not sure how to achieve this. Can anyone help me with this?
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
export const MyComponent = (props) => {
const {
visible,
setVisible,
products,
setProducts,
} = props;
const onHandleSave = (e, product) => {
e.stopPropagation();
setProducts(product)
setVisible(visible);
};
const onHandleClick = (e, product) => {
e.stopPropagation();
setProducts(product);
setVisible(!visible);
};
const onToggle = () => {
setVisible(!visible);
};
const mapProducts = () => {
if (products === undefined) {
return [];
}
return products.sort().map((product, key) => (
<Modal
className="product-option"
id="product"
key={key}
onClick={(e) => onHandleClick(e, product)}
>
{product.text}
<div className="set-default">
<span role="button" id="save-selection" tabIndex="0" onClick={(e) => onHandleSave(e, product)} aria-hidden="true" className="hovered-icon">
<Tooltip className="tooltip-content">
<small>Save</small>
</Tooltip>
</span>
</div>
</Modal>
));
};
return (
<div className="wrapper">
<button
className="products"
onClick={onToggle}
id="products"
>
{selectedItem.text}
</ button>
{visible && (
<Modal>
<div className="product-content">{mapProducts()}</div>
</Modal>
)}
</div>
);
};
export default MyComponent;
So far, I have tried to write the test like this:
import React from 'react';
import { shallow, shallowWithIntl } from 'enzyme';
import { MyComponent } from '.';
describe('<Product />', () => {
let props;
let wrapper;
beforeEach(() => {
props = {
visible: true,
setVisible: jest.fn(() => 'setVisible'),
onToggle: jest.fn(() => 'onToggleCurrency'),
onHandleClick: jest.fn(() => 'onHandleClick'),
onHandleSave: jest.fn(() => 'onHandleSave'),
};
wrapper = shallow(<MyComponent {...props} />);
});
it('check for visibility states', () => {
wrapper = shallow(<MyComponent {...props} />);
wrapper.instance().onHandleSave = jest.fn();
wrapper.instance().forceUpdate();
expect(wrapper.instance().onHandleSave).toBeCalledWith({ visible: true });
});
But no luck as the test fails. Any helps would be highly appreciated. Thanks in advance
Accessing the internal functions this way will not work, because you don't have access to the scope inside the function. These consts are technically private!
You could do it otherwise and make them accessible, but I wouldn't recommend you to do that. I'd recommend you to test from a perspective which is as close to the user's experience:
The user clicked the save -> Unit test needs to assert that the callback to setVisible and setProducts was called correctly. By accessing the mocks on props.setVisible (for example), you will be able to assert that this was called.
For further reading: https://www.stevethedev.com/blog/programming/public-private-and-protected-scope-javascript
So what you would be looking for actually is a way to click the button. Using Enzyme, you'll find plenty of resources in the net on how to do that.
Since you asked, you could make these functions accessible by setting them as class methods, adding them to the prototype, or by setting them as attributes of the component function-object:
const myFunction = () => {}
myFunction.foo = 'bar'
// you can now access myFunction.foo, as in JavaScript even a function
// is an object
To my experience you should refrain from doing so, but since you asked, here's an answer.

Trigger child function from parent component using react hooks

I have some action buttons in parent components. On click of one of such buttons, I would like to trigger a function in the child component. Currently, I am trying to implement it using useRef hook. But the solution seems tedious and also gives me warning:
My current code looks like:
import React, {useContext, useEffect, useState, useRef} from 'react';
const ParentComponent = ({...props})=> {
const myRef = useRef();
const onClickFunction = () => {
if(myRef.current) {
myRef.current.childFunction();
}
}
return (
<ChildComponent ref = {myRef}/>
);
}
Child component
const ChildComponent = (({}, ref,{ actionButtons, ...props}) => {
const [childDataApi, setChildDataApi] = useState(null);
const childFunction = () => {
//update childDataApi and pass it to parent
console.log("inside refreshEntireGrid");
}
});
Firstly, is there a better solution then trying to trigger childFunction from parent ? For this I am following this solution:
Can't access child function from parent function with React Hooks
I tried adding forward ref but that threw error as well.
I also found out that lifting the state up could be another solution as well. But I am not able to understand how to apply that solution in my case. Can someone please help me with this.
The warning says you were using forwardRef so with your snippet const ChildComponent = (({}, ref, { actionButtons, ...props }) => { .... } I'll assume this is a typo in your question and you were actually doing const ChildComponent = React.forwardRef(({}, ref,{ actionButtons, ...props }) => { .... }).
The issue here, and the warning message points this out, is that you are passing a third argument to forwardRef when it only consumes two. It seems you destructure nothing from the first props argument. From what I can tell you should replace the first argument with the third where it looks like you are doing some props destructuring.
const ChildComponent = React.forwardRef(({ actionButtons, ...props }, ref) => { .... }
From here you should implement the useImperativeHandle hook to expose out the function from the child.
const ChildComponent = React.forwardRef(({ actionButtons, ...props }, ref) => {
const [childDataApi, setChildDataApi] = useState(null);
const childFunction = () => {
// update childDataApi and pass it to parent
console.log("inside refreshEntireGrid");
}
useImperativeHandle(ref, () => ({
childFunction
}));
...
return ( ... );
});
In the parent component:
const ParentComponent = (props) => {
const myRef = useRef();
const onClickFunction = () => {
myRef.current?.childFunction();
}
return (
<ChildComponent ref={myRef}/>
);
}
Something else you can try is to pass a prop to the child to indicate that the button has been clicked and use useEffect in the child component to do something when that value changes.
const Child = props => {
useEffect(() => TriggeredFunc(), [props.buttonClicked]);
const TriggeredFunc = () => {
...
}
return '...';
}
const Parent = () => {
const [buttonClicked, setButtonClicked] = useState(0);
const onClick = e => {
setButtonClicked(buttonClicked++);
}
return <>
<button onClick={onClick}>My Button</button>
<Child buttonClicked={buttonClicked} />;
</>
}

Testing effect of provider updates

I'm attempting to flesh put my understanding of Jest testing as some of my components are lacking subsequent testing. Consider this:
import React, { ReactElement, useEffect, useState } from "react";
import { IPlaylist } from "src/interfaces/playlist";
import usePlaylists from "src/providers/playlists/hooks/use-playlists";
const JestTesting = (): ReactElement => {
const { playlists, addPlaylist } = usePlaylists();
const examplePlaylist = {
id: `${Math.floor(Math.random() * 1000)}`,
name: "testing",
created: new Date().toISOString(),
createdBy: "Biggus Dickus",
updated: new Date().toISOString(),
version: 0,
tracks: []
};
const createPlaylist = () =>
addPlaylist(examplePlaylist);
useEffect(() => {
if (playlists.length > 0) {
console.log("playlists updated")
}
}, [playlists]);
return (
<div>
<h2>Jest Testing</h2>
{playlists.map((p) => <h3 key={p.id}>{p.name}</h3>)}
<button onClick={ () => createPlaylist() }>
Create New Playlist
</button>
</div>
);
};
export default JestTesting;
This is just a very simple component that leverages the custom provider I made; the provider (in this component) has an initial playlist value of an empty array and a function for updating that array.
Here's the test:
import React from "react";
import { render, RenderResult, fireEvent, screen } from "#testing-library/react";
import JestTesting from "..";
const mockPlaylists = jest.fn().mockReturnValue([]);
const mockAddPlaylist = jest.fn();
// First mock provider attempt
jest.mock("src/providers/playlists/hooks/use-playlists", () => () => {
return {
playlists: mockPlaylists(),
addPlaylist: mockAddPlaylist
};
});
const clickCreatNewPlaylistBtn = () =>
fireEvent.click(screen.getByText("Create New Playlist"));
describe("JestTesting", () => {
let rendered: RenderResult;
const renderComponent = () => render(<JestTesting />);
beforeEach(() => {
rendered = renderComponent();
});
it("renders the component", () => {
expect(rendered.container).toMatchSnapshot();
});
describe("when new playlist is created", () => {
it("updates view/snapshot", () => {
clickCreatNewPlaylistBtn();
expect(mockAddPlaylist).toHaveBeenCalled();
expect(rendered.container).toMatchSnapshot();
});
});
});
which spits out this snapshot:
// Jest Snapshot v1,
exports[`JestTesting renders the component 1`] = `
<div>
<div>
<h2>
Jest Testing
</h2>
<button>
Create New Playlist
</button>
</div>
</div>
`;
exports[`JestTesting when new playlist is created updates view/snapshot 1`] = `
<div>
<div>
<h2>
Jest Testing
</h2>
<button>
Create New Playlist
</button>
</div>
</div>
`;
The tests pass and I can see that the mockAddPlaylist function is called, but the problem is that the playlists array in the snapshot is never updated and the snapshots don't change between the first and the second test.
What am I doing wrong here? Do I need to wait for the effects of the addPlaylist function to finish? If so, what's the best way to do?
Thanks!

mobx-react-lite useObserver hook outside of render

I've seen examples of the useObserver hook that look like this:
const Test = () => {
const store = useContext(storeContext);
return useObserver(() => (
<div>
<div>{store.num}</div>
</div>
))
}
But the following works too, and I'd like to know if there's any reason not to use useObserver to return a value that will be used in render rather than to return the render.
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return (
<div>
<div>{num}</div>
</div>
)
}
Also, I don't get any errors using useObserver twice in the same component. Any problems with something like this?
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return useObserver(() => (
<div>
<div>{num}</div>
<div>{store.num2}</div>
</div>
))
}
You can use observer method in the component. And use any store you want.
import { observer } from "mobx-react-lite";
import { useStore } from "../../stores/StoreContext";
const Test = observer(() => {
const { myStore } = useStore();
return() => (
<div>
<div>{myStore.num}</div>
<div>{myStore.num2}</div>
</div>
)
}
);
StoreContext.ts
import myStore from './myStore'
export class RootStore{
//Define your stores here. also import them in the imports
myStore = newMyStore(this)
}
export const rootStore = new RootStore();
const StoreContext = React.createContext(rootStore);
export const useStore = () => React.useContext(StoreContext);

wrapper.find is not a function

I am trying test a component in React w/ TypeScript using Jest and Enzyme.
My test is as follows:
import * as React from 'react';
import { shallow } from 'enzyme';
import * as sinon from 'sinon';
import { ButtonGroup } from '../../../src/components';
describe('.ButtonGroup', () => {
it('should render', () => {
const { wrapper } = setup({});
expect(wrapper.exists()).toBe(true);
});
it('should call the rightHandler handler on click', () => {
const onClickHandler = sinon.spy();
const wrapper = setup({ rightHandler: onClickHandler });
wrapper.find('a').simulate('click');
expect(onClickHandler).toHaveBeenCalled();
});
});
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return { wrapper };
};
const MOCK_LEFT = { type: 'reset', className: 'is-light', value: 'Reset' };
const MOCK_RIGHT = { type: 'button', className: 'is-primary', value: 'Search' };
However I am getting an error: TypeError: wrapper.find is not a function
The component I am testing looks like
import * as React from 'react';
const ButtonGroup = ({ leftBtn, rightBtn, leftHandler = null, rightHandler = null }) => (
<div className="field is-grouped is-grouped-right">
<p className="control">
<input type={leftBtn.type} className={'button ' + leftBtn.className} value={leftBtn.value} onClick={leftHandler} />
</p>
<p className="control">
<input type={rightBtn.type} className={'button ' + rightBtn.className} value={rightBtn.value} onClick={rightHandler} />
</p>
</div>
);
export default ButtonGroup;
I would like to essentially asset that on click, the action I expect is called.
Try
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return wrapper;
};
Doing return { wrapper } is the same as return { wrapper: wrapper } so it's not your DOM element anymore, but an object with a property wrapper containing your DOM element.
This is a small bin to illustrate the issue.

Resources