Failing to mock child react component in tests - reactjs

There are a bunch of articles and examples about this one, but for some reason, nothing seems to work.
I have a react component that has a child component. And, for simplicity's sake, I want to mock the child component in the test.
The component looks like this:
...
import { ProjectTeam } from '../../assignment/project-team/component';
export const ProjectOverview: React.FC<ProjectOverviewProps> = ({ projectId }) => {
...
return (
<>
...
<Box flex={1}>
<ProjectTeam projectId={projectId} />
</Box>
...
</>
);
};
The ProjectTeam component:
export const ProjectTeam: React.FC<ProjectTeamProps> = ({ projectId }) => {
// just a standard component...
};
And here is the test:
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { render } from '#testing-library/react';
import { I18nextProvider } from 'react-i18next';
import { generateProject } from '../../testing/generators/project';
import { ProjectOverview } from './component';
import { NGStateProvider } from '../../react/providers/route-provider/component';
import { testAngularRouter } from '../../testing/testAngularRouter';
import { DefaultProjectCollection, DefaultUtilizationCollection } from '../store/model';
import { DefaultApaActionCollection } from '../../apa/model';
import i18n from '../../testing/i18n';
import thunk from 'redux-thunk';
describe('ProjectOverview', () => {
let mockStore = null;
const project = generateProject();
beforeEach(() => {
jest.mock('../../assignment/project-team/component', () => ({ ProjectTeam: 'ProjectTeam' }));
mockStore = configureMockStore([thunk])({
projects: { DefaultProjectCollection, ...{ entities: { [project.id]: project } } },
toolUtilizations: DefaultUtilizationCollection,
apaActions: DefaultApaActionCollection,
});
});
test('renders correctly', () => {
const { asFragment } = render(
<NGStateProvider router={testAngularRouter}>
<Provider store={mockStore}>
<I18nextProvider i18n={i18n}>
<ProjectOverview projectId={project.id} />
</I18nextProvider>
</Provider>
</NGStateProvider>,
);
expect(asFragment()).toMatchSnapshot();
});
});
My assumption, that jest.mock(...) should simply replace the child component in test mode. However, it does not. The whole component is trying to render as is.
Here is one of the articles I was referring to: https://thoughtbot.com/blog/mocking-react-components-with-jest

Try moving the jest.mock call outside. I would say right at the top, just before and outside the describe section.
Jest needs to know about mocked component before it starts executing the test file in question.
The article which you reference, has this info,
Alternatively, you can put it within a __mocks__ folder next to the component if that is your preference.

Related

How to snapshot test connected component that wrapped with connected component?

I need to ask about testing React using enzyme and jest. The case is I have a redux connected component wrapped by also connected component.
wrapper.js
import React from "react";
import { connect } from "react-redux";
import { state, actions } from "src/services/common-store/user";
function ApplicationLayout(props) {
return (
<div>
<h1>{props.title}</h1>
<div id="content">
{props.children}
</div>
</div>
);
}
export default connect(state, actions)(ApplicationLayout);
User.js
import React from "react";
import { connect } from "react-redux";
import { state, actions } from "src/services/common-store/user";
import ApplicationLayout from './ApplicationLayout';
function User(props) {
return (
<ApplicationLayout>
<h1>{props.user.name}</h1>
<img src={props.user.img} />
</ApplicationLayout>
);
}
export default connect(state, actions)(User);
in my test file, I already provided Provider with redux mock store.
User.test.js
import React from "react";
import { mount } from "enzyme";
import configureMockStore from "redux-mock-store";
import { Provider } from "react-redux";
import toJson from "enzyme-to-json";
import thunk from "redux-thunk";
import User from "./User.js";
// Mocking Redux store
const mockStore = configureMockStore([thunk]);
const store = mockStore({ title: "Application Layout", user: { name: "andri", img: "https://ava.com/img.jpg" } });
const setup = () => {
return mount(
<Provider store={store}>
<User />
</Provider>
);
};
describe("Test Component", () => {
let wrapper;
beforeEach(() => {
wrapper = setup();
});
describe("Components rendering", () => {
it("Should render without error and match snapshot", () => {
expect(toJson(wrapper)).toMatchSnapshot();
});
});
});
but I keep getting error said that props like title and user are undefined TypeError: Cannot read property 'user' of undefined. what can I do to address this?

Problems testing a Redux + React app with enzyme:

I have this component
import React, { useEffect } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { CircularProgress } from '#material-ui/core';
import { loadPhones } from './redux/actions/actions.js';
import TablePhones from './Table.js';
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadPhones: () => {
dispatch(loadPhones());
},
};
}
export function App(props) {
useEffect(() => {
props.loadPhones();
}, []);
if (props.phones.data) {
return (
<div className="App">
<div className="introductoryNav">Phones</div>
<TablePhones phones={props.phones.data} />
</div>
);
}
return (
<div className="gridLoadingContainer">
<CircularProgress color="secondary" iconStyle="width: 1000, height:1000" />
<p className="loadingText1">Loading...</p>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
For whom ive written
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from "react-redux";
import App from './App';
import { shallow, mount } from "enzyme";
import configureMockStore from "redux-mock-store";
const mockStore = configureMockStore();
const store = mockStore({});
describe('App comp testing', () => {
it("should render without throwing an error", () => {
const app = mount(
<Provider store={store}>
<App />
</Provider>
).dive()
expect(app.find('.introductoryNav').text()).toContain("Phones");
});
})
But that test keeps failing
ypeError: Cannot read property 'data' of undefined
I also tried importing App as {App} instead and using shallow testing, but no luck. It gives the same erros, so im left without access to the context, and I cant keep doing my tests
How can I solve this?
You could use the non-default export of your component here and shallow render test if you pass your component the props and don't try to mock the store (if I recall correctly).
I was thinking something like this might work, tesing the "pure" non-store connected version of the component. This seems to be a popular answer for this question as this was asked (in a different way) before here:
import React from 'react';
import { App } from './App';
import { shallow } from "enzyme";
// useful function that is reusable for desturcturing the returned
// wrapper and object of props inside of beforeAll etc...
const setupFunc = overrideProps => {
const props = {
phones: {
...phones, // replace with a mock example of a render of props.phones
data: {
...phoneData // replace with a mock example of a render of props.phones.data
},
},
loadPhones: jest.fn()
};
const wrapper = shallow(<App {...props} />);
return {
wrapper,
props
};
};
// this is just the way I personally write my inital describe, I find it the easiest way
// to describe the component being rendered. (alot of the things below will be opinios on test improvements as well).
describe('<App />', () => {
describe('When the component has intially rendered' () => {
beforeAll(() => {
const { props } = setupFunc();
});
it ('should call loadPhones after the component has initially rendered, () => {
expect(props.loadPhones).toHaveBeenCalled();
});
});
describe('When it renders WITH props present', () => {
// we should use describes to section our tests as per how the code is written
// 1. when it renders with the props present in the component
// 2. when it renders without the props
beforeAll(() => {
const { wrapper, props } = setupFunc();
});
// "render without throwing an error" sounds quite broad or more like
// how you would "describe" how it rendered before testing something
// inside of the render. We want to have our "it" represent what we're
// actually testing; that introductoryNave has rendered with text.
it("should render an introductoryNav with text", () => {
// toContain is a bit broad, toBe would be more specific
expect(wrapper.find('.introductoryNav').text()).toBe("Phones");
});
it("should render a TablePhones component with data from props", () => {
// iirc toEqual should work here, you might need toStrictEqual though.
expect(wrapper.find('TablePhones').prop('phones')).toEqual(props.phones);
});
});
describe('When it renders WITHOUT props present', () => {
it("should render with some loading components", () => {
expect(wrapper.find('.gridLoadingContainer').exists()).toBeTruthy();
expect(wrapper.find('CircularProgress').exists()).toBeTruthy();
expect(wrapper.find('.loadingText1').exists()).toBeTruthy();
});
});
});

Is it possible to wrap each individual test in a wrapper component?

I am using material-UI components in my React components. Therefore I will need to apply the <MuiThemeProvider></MuiThemeProvider> component around all my components in my tests.
My components are located in individual folders:
./src/components/Header/Header.tsx
./src/components/Header/Header.test.ts
./src/components/Header/...
./src/components/Footer/Footer.tsx
./src/components/Footer/Footer.test.ts
./src/components/Footer/...
// etc.
A test would have to look like the following:
import React from 'react';
import { render } from '#testing-library/react';
import Header from './Header';
it('matches snapshot', () => {
const container = render(
// This theme provider is necessary since my components depend on it.
// But rather don't want to include this in all my components.
<MuiThemeProvider theme={theme}>
<Header />
</MuiThemeProvider>
);
expect(container).toMatchSnapshot();
});
But now I'd have to define the MuiThemeProvider in each of my tests.
Is it possible to do this once for all my tests?
Found it: https://testing-library.com/docs/react-testing-library/setup
Turns out it's a react-testing-library thing.
You can modify the render function to wrap a component (provider) in the render method.
Simply change the import of the render function:
Create the function containing with the 'wrapper' component:
// test-utils.js
import { render } from '#testing-library/react'
import { ThemeProvider } from 'my-ui-lib'
import { TranslationProvider } from 'my-i18n-lib'
import defaultStrings from 'i18n/en-x-default'
const AllTheProviders = ({ children }) => {
return (
<ThemeProvider theme="light"> // here it is
<TranslationProvider messages={defaultStrings}>
{children}
</TranslationProvider>
</ThemeProvider>
)
}
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options })
// re-export everything
export * from '#testing-library/react'
// override render method
export { customRender as render }
And then start using it like this:
// my-component.test.js
- import { render, fireEvent } from '#testing-library/react';
+ import { render, fireEvent } from '../test-utils';
You can mock your higher-order component using jest.mock.
It can something like following
jest.mock("hoc-module", () => {
return {
hocFunction: function(hocConf) {
return function(component) {
component.defaultProps = {
...component.defaultProps,
mockPropKey: mockPropValue
};
return component;
};
}
};
});
P.S: I wrote the above code on the fly.

Field must be inside a component decorated with reduxForm(), Error in test file

When i run my tests, i get the error:
Field must be inside a component decorated with reduxForm()
I am mocking a store, so i would think that would take care of injecting redux on the test but, i'm not really sure.
Inside appointments.js I have a component that has a redux form
import React from 'react';
... other imports
import configureMockStore from 'redux-mock-store';
import { mount } from 'enzyme';
import expect from 'expect';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
import LoginSection from '../User/LoginSection';
import AppointmentsContainer from './AppointmentsContainer';
import Appointments from './Appointments';
import AppointmentStatus from .../Layout/AppointmentStatus/AppointmentStatusContainer';
jest.mock('./Appointments');
jest.mock('../User/LoginSection');
jest.mock('../Layout/AppointmentStatus/AppointmentStatusContainer');
const store = configureMockStore()({
form: 'Appointments',
});
const setup = (newProps) => {
const props = {
handleSubmit: jest.fn(),
},
form: 'appointmentsContainer',
locale: 'en',
...newProps,
};
const root = mount(
<Provider store={store}>
<IntlProvider {...props}>
<AppointmentsContainer {...props} />
</IntlProvider>
</Provider>
,
);
const wrapper = root.find(Appointments);
return {
root,
wrapper,
props,
};
};
describe('AppointmentsContainer', () => {
beforeEach(() => {
store.clearActions();
});
Any idea how can i fix this?

Why enzyme tests did not work in React.js?

I am using Enzyme tests within Create-React-App. In shallow rendering it works fine, but mount rendering throws error:
TypeError: Cannot read property 'favorites' of undefined
Test file looks like this:
import React, { Component } from "react";
import configureMockStore from "redux-mock-store";
import { shallow, mount } from "enzyme";
import { Provider } from "react-redux";
import Favorites from "../Landing/Favorites";
const mockStore = configureMockStore();
const store = mockStore({});
function setup() {
const props = {
favorites: 42
};
const wrapper = mount(
<Provider store={store}>
<Favorites {...props} />
</Provider>
);
return {
props,
wrapper
};
}
describe("Favorites component", () => {
const { wrapper } = setup();
it("should render list of favorites cards", () => {
expect(wrapper.prop("favorites")).toEqual(42);
});
});
Why did it happen?
.prop works different in mount and shallow. You can check the docs.
http://airbnb.io/enzyme/docs/api/ReactWrapper/prop.html
http://airbnb.io/enzyme/docs/api/ShallowWrapper/prop.html
When using mount, you can directly render Favorites component.
mount(<Favorites {...props} />)

Resources