Reputation: 4163
I'm writing tests for a component that has Link
from react-router-dom
.
The component itself works without giving any error or warning.
However, when the component is rendered with shallow
from enzyme
, it shows the following error message in the console.
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
As far as I've researched, apparently this error occurs when you wrongly import a React module (or a component).
e.g.
import Link from 'react-router-dom'; // Wrong!
import { Link } from 'react-router-dom'; // Correct!
However, in my case, Link
is correctly imported but it still gives the warning message above.
Here's a snippet of the component.
import React from 'react';
import { Link, useHistory } from 'react-router-dom';
// other modules here
const ListItem = (props) => {
const history = useHistory();
const queryParameter = _.get(history, 'location.search', '');
const pathName = `/blah/${props.id}${queryParameter}`;
return (
<li>
<Link to={pathName}>
<div>blah blah...</div>
</Link>
</li>
);
};
(When I comment-out <Link>
, the warning message will be gone from the console. So, using useHistory()
should have nothing to do with the warning)
jest.mock('react-router-dom', () => ({
useHistory: jest.fn()
}));
describe('ListItem', () => {
const baseProps = { /* initial props here*/ };
it("renders the name", () => {
const wrapper = shallow(<ListItem {...baseProps} />);
// the line above shows the warning message in the console
});
});
I'm completely clueless. Any advice will be appreciated.
Upvotes: 5
Views: 3357
Reputation: 1985
In my case I imported component not directly from the place where component is.
Instead of:
import { BasicAccordion } from '../../lists/BasicAccordion';
I imported component from index.ts that exports all components
import { BasicAccordion } from '../../..';
Upvotes: 0
Reputation: 56
I ran into this issue as well today, and this is the only place I've ever seen with anyone experiencing the same thing. After banging my head against the wall for an hour, I finally figured it out—and indeed, this is probably the cause of your issue as well. It's been 2 years since you asked this so you probably don't need the answer anymore, but for any future developers stumbling across this post...
The component is importing correctly in practice but not in your tests because of your mock, specifically this part:
jest.mock('react-router-dom', () => ({
useHistory: jest.fn()
}));
This replaces all of the react-router-dom
import with an object containing only the property useHistory
, so importing Link
from it gives you an undefined. It's easily fixed as so:
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: jest.fn()
}));
Upvotes: 4
Reputation: 19762
Sounds like you're not importing the correct file (if you have a .(s)css
or test.js
file that share the same name as the component and reside within the same root directory, then the wrong file may be imported by webpack into the test file -- as such, I'd recommend specifying the component name and extension: index.js
or ListItem.js
). Otherwise, you likely forgot to export the ListItem
component.
Working example (this example uses mount
and Router
-- click the Test
tab to run the test):
components/ListItem/index.js
import React from "react";
import get from "lodash/get";
import { Link, useHistory } from "react-router-dom";
function ListItem({ children, id, destination }) {
const history = useHistory();
const query = get(history, ["location", "search"]);
return (
<li style={{ margin: 0, padding: 0 }}>
<Link to={`${destination}/${id}${query}`}>
<div>{children}</div>
</Link>
</li>
);
}
export default ListItem;
components/ListItem/__tests__/ListItem.test.js
import React from "react";
import { mount } from "enzyme";
import { Router } from "react-router-dom";
import { createMemoryHistory } from "history";
import ListItem from "../index.js";
const children = "Test";
const id = 1;
const destination = "/home";
const query = "?id=1";
const initialPath = `${destination}/${id}${query}`;
const history = createMemoryHistory({
initialEntries: [initialPath]
});
const initProps = {
children,
id,
destination
};
const wrapper = mount(
<Router history={history}>
<ListItem {...initProps} />
</Router>
);
describe("ListItem", () => {
afterAll(() => {
wrapper.unmount();
});
it("renders the children", () => {
expect(wrapper.find("div").text()).toEqual(children);
});
it("renders a Link that contains destination, params.id and query", () => {
expect(wrapper.find("Link").props().to).toEqual(
`${destination}/${id}${query}`
);
});
});
Upvotes: 0