Hiroki
Hiroki

Reputation: 4163

Jest & react-router-dom: React.createElement: type is invalid -- expected a string (for built-in components) ... but got: undefined

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

Answers (3)

Konrad Grzyb
Konrad Grzyb

Reputation: 1985

WRONG IMPORT PATH

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

jzw
jzw

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

Matt Carlotta
Matt Carlotta

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):

Edit Testing RRD


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

Related Questions