I want to ensure that a HOC component is being called with jest, but I can't seem to get jest.mock to work. My HOC is like this:
const withEntity = (
...args
) => {
const wrappedComponent = WrappedComponent => {
const innerComponent = ({ ...props }) => {
return (
<WrapperComponent
{...props}
>
<WrappedComponent />
</WrapperComponent>
);
};
innerComponent.propTypes = {
...
};
return innerComponent;
};
wrappedComponent.propTypes = {
...
};
return wrappedComponent;
};
withEntity.propTypes = {
...
};
export default withEntity;
In a separate file, the withEntity function is called like this:
export const DoSomething = withEntity(...args)(MyComponent);
Then, in the testing file for the DoSomething component, i'm trying to import the withEntity function and mock it like so:
import withEntity from "../../../shared/entity/higher_order_components/withEntity";
jest.mock("../../../shared/entity/higher_order_components/withEntity");
But when I actually attempt to run the test, I get this error:
● Test suite failed to run
TypeError: (0 , _withEntity.default)(...) is not a function
Not sure what to make of that error, what am I doing wrong here?
Mocking your HOC should look like this:
jest.mock('../your/HOC', () => () =>
Component => props => <Component {...props} />
)
it can be read as :
jest.mock('../your/HOC', () => `
create a mock that returns your HOC function,
() =>
the function that returns your HOC aka withEntity(...args),
Component => props => <Component {...props} />
the HOC itself, which is a function that gets the component and return a function that get the props and return a function that returns the rendered component with its props.
In my case because I am using typescript this is what works for me.
jest.mock('components/MyComponent', () => ({
__esModule: true,
default({children}: any) {
return children(() => {});
},
}));
What worked for me is basically putting the below snippet into setupTests.js if you wish the render your component without the HOC affecting it.
jest.mock('./pathTo/yourHOC', () => Component => {
return Component
})
This works for me
.mock(
'./pathTo/yourHOC',
() => () => (Component) => {
return Component;
})
Related
I would like to create the exact same behaviour in function component ref as there is in class component ref.
Heres an example:
const AnotherComponent = () => {
const DoSomething () => console.log(something);
return <div> There is a content </div>;
}
const MyComponent () => {
let newRef = useRef();
useEffect(() => {
newRef.current.DoSomething();
}, []);
return <AnotherComponent ref={newRef} />;
}
I know about forwardRef, but from what I understand it allows you to bind the ref to a specific input to get its events, but not to the WHOLE component with all its functions.
Is it even possible to achieve this with function components ?
Yes, it's completely possible. React refs are for more than just getting access to DOMNodes. Use a combination of React.forwardRef and the useImperativeHandle React hook to forward a React ref to a function component and expose out functions/values.
const AnotherComponent = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
doSomething
}));
const doSomething () => console.log(something);
return <div> There is a content </div>;
});
...
const MyComponent () => {
const newRef = useRef();
useEffect(() => {
newRef.current.doSomething();
}, []);
return <AnotherComponent ref={newRef} />;
}
Forwarding refs
forwardRef
useImperativeHandle
I have a component in my React codebase and I am trying to create some snapshot tests for it, but unfortunately there is some weird error going on. Jest implies that I'm doing some destructuring on non-iterable instance and logs shows something that however much I tried to think about it, it would work perfectly, but I don't know what is wrong with this snippet of code shown in image below:
Also please note that I did a mock for formik in my component like below:
jest.mock('formik', () => ({
useField: jest.fn(),
useFormikContext: jest.fn(),
}));
and every other question related to the topic either is not for Jest or answer is not working and not even accepted!
Here is my test file:
import React from 'react';
import renderer from 'react-test-renderer';
import MyComponent from '../MyComponent';
jest.mock('../MyComponentManager');
jest.mock('formik', () => ({
useField: jest.fn(),
useFormikContext: jest.fn().mockReturnValue({ isValidating: false, setFieldValue: () => {} }),
}));
describe('MyComponent', () => {
test('should render component properly', () => {
const component = renderer.create(<MyComponent />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
And here is the minor version of the component that is being testing:
const MyComponent = () => {
<SomeWrapperComponent>
...some jsx
<NumberField {...props} />
</SomeWrapperComponent>
}
and the error happens in side the <NumberField /> component.
I believe your problem is setting useFormikContext: jest.fn(). jest.fn() by default returns a function that returns undefined as mentioned in jest docs. and by that you try to deconstruct undefined.
you need to mock the return value of useFormikContext function:
jest.mock('formik', () => ({
useField: jest.fn(),
useFormikContext: jest.fn().mockReturnValue({ isValidating: false, setFieldValue: () => {} }),
}));
If that doesnt help, please add full and simple runnable example that's not working, so I can take a deeper look. (i.e. test file and code file)
Edit
I see you only mocked useField useFormikContext functions of formik module, although you probably use more from formik module, like Formik tag and such, but they're now overriden.
jest.mock overrides the whole module, and not suitable for your use case, because you only want to mock only useField & useFormikContext.
I like to use jest.spyOn which can be used to mock only specific functions of a module. see jest docs on jest.spyOn.
Here's full working small example mocking formik in jest react testing.
example.js - react component that's being tested.
example.spec.js - simple jest test
example.js:
import React from 'react';
import { useFormikContext, Formik, Form, Field } from 'formik';
export const OtherComponent = () => {
// Grab values and submitForm from context
const { isValidating, setFieldValue } = useFormikContext();
return (
<div>
<div>{isValidating ? 'validating...' : 'validated!'}</div>
<button type="submit" disabled={isValidating}>
submit
</button>
<button
type="button"
disabled={isValidating}
onClick={() => {
setFieldValue('token', '123456');
}}
>
return to default
</button>
</div>
);
};
export const ExmpleComponent = () => {
return (
<div>
<h1>Example</h1>
<Formik
initialValues={{ token: '' }}
validate={(values) => {
return new Promise((resolve, _reject) => setTimeout(resolve, 3000)).then(() => {
// wait 3 seconds
const errors = {}; // no errors
return errors;
});
}}
onSubmit={(_values, _actions) => {
console.log('submitted!');
}}
>
<Form>
<Field name="token" type="tel" />
<OtherComponent />
</Form>
</Formik>
</div>
);
};
example.spec.js:
import React from 'react';
import renderer from 'react-test-renderer';
import * as formik from 'formik';
import { ExmpleComponent } from '../src/example.js';
// jest.mock('formik', () => ({
// useField: jest.fn(),
// useFormikContext: jest.fn().mockReturnValue({ isValidating: false, setFieldValue: () => {} }),
// }));
describe('MyComponent', () => {
test('should render component properly', () => {
const useFieldSpy = jest.spyOn(formik, 'useField');
const useFormikContextSpy = jest.spyOn(formik, 'useFormikContext');
useFieldSpy.mockReturnValue(undefined);
useFormikContextSpy.mockReturnValue({ isValidating: true, setFieldValue: () => {} });
const component = renderer.create(<ExmpleComponent />);
const tree = component.toJSON();
expect(tree.children[1].children[1].children[0].children[0]).toBe('validating...');
useFieldSpy.mockRestore();
useFormikContextSpy.mockRestore();
});
});
If that still doesnt work, please add full & simple example that I can run to test by myself to see what's wrong in your code. You added full example of the test file, but not the code you're testing.
For contribute, I solved similar issue using this code:
jest.mock('formik', () => ({
useField: jest.fn(),
useFormikContext: jest.fn().mockReturnValue({
setFieldValue: () => {},
handleChange: () => {},
initialValues: {} as AddDoctorInfoFormValues,
errors: {} as FormikErrors<AddDoctorInfoFormValues>,
values: {} as AddDoctorInfoFormValues,
}),
}));
Can we dynamically import hooks based on the value passed to the component?
For eg.
App.js
<BaseComponent isActive />
BaseComponent.js
if(props.isActive) {
// import useActive (custom Hook)
}
I donot want these(custom hook) files to be imported and increase the size of BaseComponent even when the props contain falsey values.
You can dynamically import hooks as it is just a function (using require), but you shouldn't because you can't use hooks inside conditions.
See Rules of Hooks
Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
If you want conditionally use a hook, use the condition in its implementation (look for example at skip option of useQuery hook from Apollo GraphQL Client).
const useActive = (isUsed) => {
if (isUsed) {
// logic
}
}
You should extract the logic inside the useActive hook and dynamically import it instead of dynamically importing the hook since you should not call Hooks inside loops, conditions, or nested functions., checkout Rules of Hooks:
Let's say your useActive hook was trying to update the document title (in real world, it has to be a big chunk of code that you would consider using dynamic import)
It might be implemented as below:
// useActive.js
import { useEffect } from "react";
export function useActive() {
useEffect(() => {
document.title = "(Active) Hello World!";
}, []);
}
And you tried to use it in the BaseComponent:
// BaseComponent.js
function BaseComponent({ isActive }) {
if (isActive) { // <-- call Hooks inside conditions ❌
import("./useActive").then(({ useActive }) => {
useActive();
});
}
return <p>Base</p>;
}
Here you violated the rule "don't call Hooks inside conditions" and will get an Invalid hook call. error.
So instead of dynamically import the hook, you can extract the logic inside the hook and dynamically import it:
// updateTitle.js
export function updateTitle() {
document.title = "(Active) Hello World!"
}
And you do the isActive check inside the hook:
// BaseComponent.js
function BaseComponent({ isActive }) {
useEffect(() => {
if (!isActive) return;
import("./updateTitle").then(({ updateTitle }) => {
updateTitle();
});
}, [isActive]);
return <p>Base</p>;
}
It works fine without violating any rules of hooks.
I have attached a CodeSandbox for you to play around:
You could create a Higher Order Component that fetches the hook and then passes it down as a prop to a wrapped component. By doing so the wrapped component can use the hook without breaking the rules of hooks, eg from the wrapped component's point of view, the reference to the hook never changes and the hook gets called everytime the wrapped component renders. Here is what the code would look like:
export function withDynamicHook(hookName, importFunc, Component) {
return (props) => {
const [hook, setHook] = useState();
useEffect(() => {
importFunc().then((mod) => setHook(() => mod[hookName]));
}, []);
if (!hook) {
return null;
}
const newProps = { ...props, [hookName]: hook };
return <Component {...newProps} />;
};
}
// example of a Component using it:
const MyComponent = ({useMyHook}) => {
let something = useMyHook();
console.log(something)
return <div>myHook returned something, see the console to inspect it </div>
}
const MyComponentWithHook = withDynamicHook('useMyHook', () => import('module-containing-usemyhook'), MyComponent)
To whoever encountered it as well: You can't use Hooks inside dynamically imported components(however, apparently if you does not use hooks even the first example works):
instead of:
const useDynamicDemoImport = (name) => {
const [comp, setComp] = useState(null);
useEffect(() => {
let resolvedComp = false;
import(`#site/src/demos/${name}`)
.then((m) => {
if (!resolvedComp) {
resolvedComp = true;
setComp(m.default);
}
})
.catch(console.error);
return () => {
resolvedComp = true;
};
}, []);
return comp;
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
{comp}
</Paper>
);
};
export default DemoPreviewer
use React Lazy instead and render the component later
const useDynamicDemoImport = (name) => {
const Comp = React.lazy(() => import(`#site/src/demos/${name}`));
return comp;
};
const RootDemoPreviewer: FC<DemoPreviewerProps> = (props) => {
console.log("RootDemoPreviewer");
return (
<React.Suspense fallback={<div>Loading...</div>}>
<DemoPreviewer {...props} />
</React.Suspense>
);
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
const Comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
<Comp />
</Paper>
);
};
export default RootDemoPreviewer
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.
I am using React-Redux, in a connected component and I want to test if a particular component is rendered. In order for that component to render 2 things must be true:
ListUsers must be an empty array
The securityMode should be basic.
I have already defined the securityMode in my component Props, with no problem. But the ListUsers prop, is coming through redux.
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
This is my component logic that should be tested:
renderNoResourceComponent = () => {
const { usersList, securityMode } = this.props;
const { selectedGroups } = this.state;
const filteredData = filterUserData(usersList, selectedGroups);
if (filteredData && filteredData.length === 0 && securityMode === 'BASIC') {
return (
<div className="center-block" data-test="no-resource-component">
<NoResource>
.............
</NoResource>
</div>
);
}
return null;
};
And this is the test I wrote:
describe('BASIC securityMode without Data', () => {
const props = {
securityMode: 'BASIC',
listUsers: () => {},
usersList: [] // This is the redux prop
};
it('should render NoResource component', () => {
const wrapper = shallow(<UsersOverviewScreen {...props} />);
const renderUsers = wrapper.find(`[data-test="no-resource-component"]`);
expect(renderUsers).toHaveLength(1);
});
});
But I get an error saying the userLists is not defined. How do I pass this redux prop so my component would pass. `I also need that prop for another set of tests, that needs data, which I need to mock.
Can someone guide me through this? Thank you..
What you want to do is export the component before its connocted to Redux and pass all the props it needs manually:
export class UsersOverviewScreen extends Component {
// ... your functions
render() {
return (
// ... your componont
);
}
}
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
export default connect(mapStateToProps)(UsersOverviewScreen);
Now, in your tests you can import { UsersOverviewScreen } form 'path/to/UsersOverviewScreen';. You can create the props and pass it to the component like this:
const mockUsersLists = jest.fn(() => usersList || []);
const wrapper = shallow(<UsersOverviewScreen {...props} usersList={mockUsersLists} />);