I have a React component that has URL params. When I run the test and mount the component the params are always undefined and as a result, break the test. I have tried to hard code them as constants, props and it still won't work. Any other ideas I can try?
import React, { Component } from 'react';
class BarcodePage extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { SKU, ID } = this.props.match.params
}
render() {
return (
<h1>Barcode view {SKU} {ID}</h1>
);
}
}
export default BarcodePage;
import React from 'react';
import { shallow } from 'enzyme';
import BarcodePage from './BarcodePage';
const component = mount(
<BarcodePage params={{SKU: '1111', ID: '2121212' }} />
);
describe('<BarcodePage />', () => {
it('render one header', () => {
expect(component.find('h1').length).toBe(1);
});
})
React Router provides and your code uses this.props.match.params, not this.props.params. You're passing the wrong props to your unit test:
<BarcodePage params={{SKU: '1111', ID: '2121212' }} />
That gives you this.props.params, but it should be this.props.match.params:
<BarcodePage match={{params: {SKU: '1111', ID: '2121212' }}} />
Related
In my react app I want to send state to the next path through the history.location.state and history.location.pathname
In my case, it has to push successfully and also showing in history but when I console.log(this.props.history) in the child page showing undefined.
MyComponent Code
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class MyComponent extends Component {
state = {
cart: {
1:{
icon: "URL"
id: 1
quantity: 1
title: "item1"
}
2:{
icon: "URL"
id: 2
quantity: 1
title: "item2"
}
}
}
submitHandler = (event) => {
event.preventDefault();
let data = {};
for (let key in this.state.cart) {
data[key] = this.state.cart[key]
}
console.log("data=",data);
this.props.history.push({
pathname: "/result",
state: { data: data }
});
}
render(){
return(
<div >
<button onClick={this.submitHandler}>CONTINUE</button>
</div>
)
}
}
export default withRouter(MyComponent);
Result Component
import React, { Component } from 'react';
class Result extends Component {
render() {
console.log("ss=",this.props.history);
return(<div>Result</div>)
}
export default Result;
In Console
Route
<Route path="/result" component={Result} />
As shown in the above img in history->location->state is push fine.
But when I console log to this.props showing undefined.
Also I already use withRouter hoc of react-router-dom in export
Suggest me a solution to this?
May be you are called wrong path. its working and the state variable available on this.props.location not a this.props.history;
Codesanbox example
I have to do it a few simple React Enzyme tests. I want to check if component is rendered.
import React from 'react';
import { shallow } from 'enzyme';
import ConnSearch from './ConnSearch';
it('renders without errors', () => {
const component = shallow(<ConnSearch />);
console.log(component.debug());
});
I have results: Could not find "store" in the context of "Connect(ConnSearch)". Either wrap the root component in a , or pass a custom React context provider to a
nd the corresponding React context consumer to Connect(ConnSearch) in connect options.
My ConnSearch Component:
import React, { Component } from 'react';
import {fetchRoadDetails, fetchUserPoints} from "../../actions";
import {connect} from "react-redux";
import RoadTable from "../../components/RoadTable/RoadTable";
import RoadForm from "../../components/RoadTable/RoadForm";
import style from './ConnSearch.module.scss'
import {getPoints} from "../../reducers";
class ConnSearch extends Component {
constructor(props){
super(props);
this.state = {
};
}
componentDidMount() {
this.props.fetchUserPoints(this.props.userLogin);
}
render() {
return (
<div className={style.wrapper}>
<RoadForm />
<div className={style.tableWrapper} >
<RoadTable/>
</div>
</div>
);
}
}
const mapDispatchToProps=dispatch=>({
fetchRoadDetails:()=>dispatch(fetchRoadDetails()),
fetchUserPoints:(user)=>dispatch(fetchUserPoints(user)),
});
const mapStateToProps = (state) => {
return {
roads: state.road,
points:getPoints(state),
userLogin: state.userLogin,
};
};
export default connect(mapStateToProps,mapDispatchToProps)(ConnSearch);
How can I do this test ? I've never done that before.
Unfortunately, when I wrap it in a provider:
it('renders without errors', () => {
const component = shallow( <Provider store={store}><ConnSearch/></Provider>);
console.log(component.debug());
});
I got this:
console.log src/views/ConnectionSearch/ConnSearch.test.js:11
<ContextProvider value={{...}}>
<Connect(ConnSearch) />
</ContextProvider>
I want ConnSearch render structure.
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';
I'm trying to setup testing on a new project created with react-create-app. Which now seems to be using React 16 and Jest 3 (which supposedly had some breaking changes, or maybe that was enzime). I'm getting an error similar to this post TypeError: dispatch is not a function when I try to test a method using JEST
TypeError: dispatch is not a function
at App.componentDidMount (src/components/App.js:21:68)
import React from 'react';
import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import { App } from '../components/App';
import configureStore from '../state/store/configureStore';
window.store = configureStore({
slider: {
mainImageIndex: 0,
pageNum: 1,
perPage: 4,
},
});
const appTest = (
<Provider store={window.store}>
<App />
</Provider>
);
describe('App', () => {
it('should render without crashing', () => {
mount(appTest);
});
});
Originally I just tried to do this:
import React from 'react';
import { mount } from 'enzyme';
import { App } from '../components/App';
describe('App', () => {
it('should render without crashing', () => {
mount(<App />);
});
});
Which threw this error
Invariant Violation: Could not find "store" in either the context or props of "Connect(Form(SearchForm))". Either wrap the root component in a , or explicitly pass "store" as a prop
Code for App.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchPhotos } from '../state/actions/searchPhotos';
import { setMainImageIndex, setFirstPage } from '../state/actions/slider';
import Slider from './Slider';
import SearchForm from './SearchForm';
import Error from './Error';
import '../styles/App.css';
export class App extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch(searchPhotos(window.store));
}
searchPhotosSubmit = () => {
const { dispatch } = this.props;
dispatch(setFirstPage());
dispatch(setMainImageIndex(0));
dispatch(searchPhotos(window.store));
}
render() {
const { fetchError } = this.props;
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Flickr Slider in React.js</h1>
<SearchForm onSubmit={this.searchPhotosSubmit} />
</header>
{!fetchError ? <Slider /> : <Error />}
</div>
);
}
}
export default connect(state => ({
fetchError: state.fetchError,
form: state.form,
slider: state.slider,
}))(App);
Please not that you export both presentational component (as named export) and container component (as default export) in App.js. Then in your tests you import and use the presentational component using:
import { App } from '../components/App';
but you should import connected container component instead using:
import App from '../components/App'; // IMPORTANT! - no braces around `App`
Since you're using component that is not connected to Redux store dispatch prop is not injected as prop. Just use correct import and it should work.
For more details about importing default and named exports please check this doc. About presentational and container components you can read here.
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.