I'm very new to React testing and I'm trying to write Jest/Enzyme Unit tests for the MyReload wrapper component on top of Apollo Query that I have created. This basically adds refetchers to an array (that can be executed at later point of time).
Usage:
<MyReloadQuery id='1234 .....> // do something.. children </MyReloadQuery>
Component:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Subscribe } from "unstated";
import { Query } from "react-apollo";
import { MyReloadContainer } from "./MyReloadContainer";
export class MyReloadQuery extends Component {
static propTypes = {
children: PropTypes.any
};
componentWillUnmount() {
this.onUnmount();
}
render() {
return (
<Subscribe to={[MyReloadContainer]}>
{refetchers => (
<Query {...this.props}>
{(...args) => {
const { refetch } = args[0];
this.onUnmount = () => {
refetchers.removeRefetcher(refetch);
};
refetchers.addRefetcher(refetch);
return this.props.children(...args);
}}
</Query>
)}
</Subscribe>
);
}
}
I'm very new to React and it's testing and I'm wondering how can one test with dummy data for this one?
Can someone please enlighten me? Any help would be appreciated :pray:
Related
I am really stuck on this in the project I am working on, and all the answers I have found seem to be so simple but they haven't worked for me. Perhaps I don't really understand what a mock is, and I could really use some guidance.
I am testing a parent component that has a child component that fetches some data from a database using GraphQL. While testing the parent, I do not care what the child is doing. I want to replace the child with a mocked component (one that doesn't fetch data from a database) so that I can only focus on the parent.
I have come up with the simplest example possible to show my situation. Note that I'm using React Native and React Native Testing Library.
./src/ParentComponent.js
import React from 'react';
import { Text, View } from 'react-native';
import ChildComponent from './ChildComponent';
const ParentComponent = () => (
<View>
<Text>Hello World</Text>
<ChildComponent />
</View>
);
export default ParentComponent;
./src/ChildComponent.js
import React from 'react';
import { useQuery } from 'react-apollo';
import { View, Text } from 'react-native';
import gql from 'graphql-tag';
const CURRENT_USER_QUERY = gql`
query {
currentUser {
username
}
}
`;
const ChildComponent = () => {
const { data } = useQuery(CURRENT_USER_QUERY);
const { username } = data.currentUser;
return (
<View>
<Text>Welcome, {username}</Text>
</View>
);
};
export default ChildComponent;
./src/__mocks__/ChildComponent.js
import React from 'react';
import { Text } from 'react-native';
const ChildComponent = () => <Text>Welcome.</Text>;
export default ChildComponent;
./src/ParentComponent.test.js
import React from 'react';
import { MockedProvider } from '#apollo/react-testing';
import { render } from '#testing-library/react-native';
import ParentComponent from '../ParentComponent';
it(`should render the parent component.`, () => {
jest.mock('../ChildComponent');
const { getByText } = render(
<MockedProvider>
<ParentComponent />
</MockedProvider>
);
expect(getByText('Hello World')).toBeTruthy();
});
When I run the test, I get the following error...
● should render the parent component.
TypeError: Cannot read property 'currentUser' of undefined
14 | const ChildComponent = () => {
15 | const { data } = useQuery(CURRENT_USER_QUERY);
> 16 | const { username } = data.currentUser;
| ^
17 |
18 | return (
19 | <View>
at ChildComponent (src/ChildComponent.js:16:29)
It is still using the real <ChildComponent />. Why is it not replacing the <ChildComponent /> with the mocked version in the __mocks__ directory? Is that not how mocks work? If someone can please help and explain, it would be much appreciated. Thank you.
After days of wracking my brain on this, I finally figured out what was wrong. I was calling the mock inside of the it declaration. When I moved the line, jest.mock('../ChildComponent');, to the top, everything worked. So, the test file should look like this...
import React from 'react';
import { MockedProvider } from '#apollo/react-testing';
import { render } from '#testing-library/react-native';
import ParentComponent from '../ParentComponent';
jest.mock('../ChildComponent');
it(`should render the parent component.`, () => {
const { getByText } = render(
<MockedProvider>
<ParentComponent />
</MockedProvider>
);
expect(getByText('Hello World')).toBeTruthy();
});
I'm trying to display a dashboard component, crunching a lot of data fetched from my redux store. This component takes a lot of time to render, mainly because of a single complex method.
Is it possible to render some kind of loader or placeholder while this method is processing ?
I tried doing so by using ComponentDidMount, but it seems like, because the method is part of my render() method, it will always be triggered first-hand.
Yes! Check out this tutorial.
Loader:
import React, {Component} from 'react';
const asyncComponent = (importComponent) => {
return class extends Component {
state = {
component: null
}
componentDidMount() {
importComponent()
.then(cmp => {
this.setState({component: cmp.default});
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props}/> : null;
}
}
};
export default asyncComponent;
Usage:
import React from 'react';
import asyncComponent from '../../hoc/asyncComponent';
const AsyncButton = asyncComponent(() => {
return import('../Button');
});
const container = () => {
return (
<div>
<h1>Here goes an async loaded button component</h1>
<AsyncButton/>
</div>
);
};
export default container;
or check out this library.
I have two HOCs that add context to a component like so :
const withContextOne = Component => class extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
};
export default withContextOne;
Desired Result
I just want an syntactically concise way to wrap a component with this HOC so that it doesn't impact my JSX structure too much.
What I have tried
Exporting a component with the HOC attached export default withContextOne(withContextTwo(MyComponent)) This way is the most concise, but unfortunately it breaks my unit tests.
Trying to evaluate the HOC from within JSX like :
{ withContextOne(withContextTwo(<Component />)) }
This throws me an error saying
Functions are not valid as a React child. This may happen if you return a Component instead of < Component /> from render.
Creating a variable to store the HOC component in before rendering :
const HOC = withContextOne(Component)
Then simply rendering with <HOC {...props}/> etc. I don't like this method as it changes the name of the component within my JSX
You can set the displayName before returning the wrapped component.
const withContextOne = Component => {
class WithContextOneHOC extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
}
WithContextOneHOC.displayName = `WithContextOneHOC(${Component.displayName})`;
return WithContextOneHOC;
};
This will put <WithContextOneHOC(YourComponentHere)> in your React tree instead of just the generic React <Component> element.
You can use decorators to ease the syntactic pain of chained HOCs. I forget which specific babel plugin you need, it might (still) be babel-plugin-transform-decorators-legacy or could be babel-plugin-transform-decorators, depending on your version of babel.
For example:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
#withRouter
#resizeOnScroll
#injectIntl
#connect(s => s, (dispatch) => ({ dispatch }))
export default class FooBar extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
However, the caveat with decorators is that testing becomes a pain. You can't use decorators with const, so if you want to export a "clean" undecorated class you're out of luck. This is what I usually do now, purely for the sake of testing:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
export class FooBarUndecorated extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
export default withRouter(
resizeOnScroll(
injectIntl(
connect(s => s, ({ dispatch }) => ({ dispatch }))(
FooBarUndecorated
)
)
)
);
// somewhere in my app
import FooBar from './FooBar';
// in a test so I don't have to use .dive().dive().dive().dive()
import { FooBarUndecorated } from 'src/components/FooBar';
Enzyme shallow rendering behaves in an unexpected way if I am rendering a redux component with a mocked store.
I have a simple test that looks like this :
import React from 'react';
import { shallow } from 'enzyme';
import { createMockStore } from 'redux-test-utils';
import Test from './Test'
it('should render ', () => {
const testState = {
app: {
bar: ['a', 'b', 'c']
}
};
const store = createMockStore(testState)
const context = {
store,
};
const shallowComponent = shallow(<Test items={[]}/>, {context});
console.log(shallowComponent.debug());
}
The Test component looks like :
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
export default Test;
Which as expected prints out this :
<div className="here" />
However if my component is a redux component :
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
const mapStateToProps = state => {
return {
barData: state.app.bar
}
}
export default connect(
mapStateToProps
)(Test)
Then what I get in the console is this :
<BarSeriesListTest items={{...}} barData={{...}} dispatch={[Function]} />
Why is there this difference? How do I test that my component has <div className="here"/> embedded in it in my redux version of the component?
You are referencing the HOC that connect is returning and not the component that you want to test.
You should use enzyme's dive function which will render the child component and return it as a wrapper.
const shallowComponent = shallow(<Test items={[]}/>, {context}).dive();
You can use it multiple times if you have multiple components that you need to dive through to get to. It's better than using mount as well because we are still testing in isolation.
You should export the unconnected component and test it separately (notice the first export):
export class Test extends React.Component {
}
...
export default connect(
mapStateToProps
)(Test)
While in your test you should test the rendering of the unconnected component like so (notice the curly braces around { Test }):
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Test } from './Test';
describe('...', () => {
it('...', () => {
const wrapper = shallow(<Test />)
expect(toJson(wrapper)).toMatchSnapshot();
})
})
Hope this helps.
Mode specifically for your described case the component should be:
import React from 'react';
import { connect } from 'react-redux';
export class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
const mapStateToProps = state => {
return {
barData: state.app.bar
}
}
export default connect(
mapStateToProps
)(Test)
The test spec should be:
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Test } from 'Test';
describe('Test component', () => {
it('renders', () => {
const wrapper = shallow(<Test />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
Which generates the following snapshot:
exports[`Test component renders 1`] = `
<div
className="here"
/>
`;
You are exporting the connected component by default. What you can do is import the component that is not connected to redux.
import { Test } from './Test';
Then your test should work.
Here is a stripped down version of my Redux/React component:
import React from "react";
import { connect } from "react-redux";
import Header from "./components/Header";
import { getMoviesInit } from "./actions/movieActions";
export class Layout extends React.Component {
componentWillMount() {
if (this.props.movies.length == 0) {
this.props.dispatch((dispatch) => {
dispatch(getMoviesInit());
});
}
}
render() {
return (
<div id="content-wrap">
<Header/>
{this.props.children}
</div>
);
}
};
export default connect((store) => {
return {
movies: store.movies
};
})(Layout);
When I try to run a test like the following, I'm told I this.props.dispatch() isn't a function which makes sense because there is a dispatch() call in the componentWillMount() function and I'm not using the connect() wrapped Layout:
import React from "react";
import LayoutWithConnect, { Layout } from "./Layout";
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
describe("Layout tests", function() {
beforeEach(() => {
let movies = {
featuredId: null,
currentTitles: {
movies: []
}
};
let subject = shallow(<Layout movies={movies} />);
});
});
However, if I try to use LayoutWithConnect in my test I get issues with there not being a store.
I'm not sure how I should be going about testing this component?
You can simply pass the dispatch function as a prop:
let dispatchedAction;
let dispatch = (action) => dispatchedAction = action;
let subject = shallow(<Layout movies={movies} dispatch={dispatch} />);
You can then also test that the view dispatched the correct action by comparing dispatchedAction with the expected action object.
If you find yourself doing this often, you can also use the excellent Sinon stubbing/mocking library, which allows you to easily create spy functions:
let dispatch = sinon.spy();
let subject = shallow(<Layout movies={movies} dispatch={dispatch} />);
expect(dispatch.calledWith(expectedAction)).to.be.true;