I wrote a react site about 6 months ago and had a suite of Jest tests, which all ran fine. I've created a second project based off this one but for some reason when I try and write the same tests on basic component rendering, they fail.
The error I get is
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ControlBar)". Either wrap the root component in a
<Provider>, or explicitly pass "store" as a prop to
"Connect(ControlBar)".
I've done some reading around and there are a few posts on similar topics, which seem to say that TypeScript/Redux aren't playing well together. However in my last project it was exactly the same as above and all the tests run fine. So not sure if it is just that I have pulled in a newer version of something which causes this breaking change, but hoping someone can point out what I'm doing wrong?
My component
interface IControlBarProps {
includeValidated: boolean,
includeValidatedChanged: (includeValidated:boolean) => void,
}
export class ControlBar extends React.Component<IControlBarProps, {}> {
constructor(props: any) {
super(props);
}
public render() { ... }
}
function mapStateToProps(state: IStoreState) {
return {
includeValidated: state.trade.includeValidated
};
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
includeValidatedChanged: (includeValidated:boolean) => {
dispatch(getIncludeValidatedChangedAction(includeValidated))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ControlBar);
My test
import ControlBar from '../ControlBar';
describe('Control Bar Component', () => {
it('should render without throwing an error', () => {
const wrapper = shallow(<ControlBar includeValidated={true} includeValidatedChanged={() => {return;}} />);
expect(wrapper.find('div.Control-bar').exists()).toEqual(true);
})
})
Import your component not connected to Redux, the named export, not the default export of the connected one.
import { ControlBar } from '../ControlBar';
You import ControlBar component wrapped with Redux (export default). To unit test ControlBar try
import { ControlBar } from '../ControlBar';
Related
I am creating a page in React. Lets say for eg. "Conatct us" page. This whole component must be reusable. So that other teams can use it as it is. This component will have its own redux store and api calls using axios.
What I want to confirm that if I export this "Contact Us" module as npm package, will it work fine for other teams? Why I am asking this is because other teams project will have their own redux store and axios instance. And I think we can have only one redux store in an app and maybe one axios interceptors (I may be wrong about axios though)
Could anyone help me out, what can be done in this case? One thing is sure that I will have to export this whole component as npm package.
I'm going to answer here to give you more details:
Let's say your component looks like this:
AboutUs:
import React, { Component } from "react";
import PropTypes from "prop-types";
export class AboutUs extends Component {
componentDidMount() {
const { fetchData } = this.props;
fetchData();
}
render() {
const { data, loading, error } = this.props;
if (loading) return <p>Loading</p>;
if (error) return <p>{error}</p>;
return (
// whatever you want to do with the data prop that comes from the fetch.
)
}
}
AboutUs.defaultProps = {
error: null,
};
// Here you declare what is needed for your component to work.
AboutUs.propTypes = {
error: PropTypes.string,
data: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
}),
fetchData: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};
This component just takes a few props in order to work and the fetchData function will be a dispatch of any redux action.
So in one of the apps that are going to use the component library, assuming that they have their own store, you could do something like this.
In the component where you're planning to use the AboutUs component.
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
// this is the action that performs the data fetching flow.
import { fetchAboutUs } from "redux-modules/aboutUs/actions";
// The component that is above
import { AboutUs } from "your-component-library";
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
fetchData: fetchDashboard,
},
dispatch
);
};
const mapStateToProps = state => ({
loading: state.aboutUsReducer.loading,
error: state.aboutUsReducer.error,
data: state.aboutUsReducer.data,
});
const ReduxAboutUs = connect(
mapStateToProps,
mapDispatchToProps
)(AboutUs);
// Use your connected redux component in the app.
const SampleComponent = () => {
return <ReduxAboutUs />
}
This ensures that your component can work out of the box without redux, because you can explicitly use it without the redux dependency and just pass regular props and it will continue working. Also if you have different applications where you are going to use it you will have the control of which part of the store you want to use to inject the props for this component. Proptypes are quite useful here, because we're enforcing a few props in order let the devs what do we need to pass in order for the component to work properly.
I am working on a first React-Redux app. I have the following container:
import { connect } from 'react-redux'
import Visualization from '../components/visualization'
// http://redux.js.org/docs/basics/UsageWithReact.html#implementing-container-components
const mapStateToProps = (state) => state; // identity transform, for now...
function mapDispatchToProps(dispatch) {
return {
stepForward: () => dispatch('STEP_FORWARD')
};
}
const VisualizationContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Visualization);
export default VisualizationContainer;
And the following subsequent component:
import React from 'react'
export default class Visualization extends React.Component {
render() {
console.log(this.props.stepForward());
return <div>"HELLO WORLD"</div>;
}
}
However, when I run this application I error out with:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
The problem, marked with XXX, is that stepForward exists, but explodes when executed. What is the error here?
It is just as the error says. An action should be an object and no other type. You're trying to dispatch a string as an action, which is invalid. Instead, you should use an object with a type property set to the action's type string.
Change your mapDispatchToProps function to:
function mapDispatchToProps(dispatch) {
return {
stepForward: () => dispatch({
type: 'STEP_FORWARD'
})
};
}
and it should work.
You should actually go a bit further than that and make all your actions follow a standardized template. A nice one a lot of React devs follow is flux-standard-action.
I have a number of React components that need to fetch certain items to display. The components could be functional components, except for a very simple componentDidMount callback. Is there a good design pattern that would allow me to return to functional components?
class Things extends React.Component {
componentDidMount() {
this.props.fetchThings()
}
render() {
things = this.props.things
...
}
}
I'm using react-redux and I'm also using connect to connect my component to my reducers and actions. Here's that file:
import { connect } from 'react-redux'
import Things from './things'
import { fetchThings } from '../../actions/thing_actions'
const mapStateToProps = ({ things }) => ({
things: things,
})
const mapDispatchToProps = () => ({
fetchThings: () => fetchThings()
})
export default connect(mapStateToProps, mapDispatchToProps)(Things)
Would it make sense to fetch the things in this file instead? Maybe something like this:
import { connect } from 'react-redux'
import Things from './things'
import { fetchThings } from '../../actions/thing_actions'
const mapStateToProps = ({ things }) => ({
things: things,
})
class ThingsContainer extends React.Component {
componentDidMount() {
fetchThings()
}
render() {
return (
<Things things={this.props.things} />
)
}
}
export default connect(mapStateToProps)(ThingsContainer)
Functional components are meant to be components that don't do anything. You just give them props and they render. In fact, if your component needs to fetch anything at all, it's likely your component should be transformed into a container which fetches the data you need. You can then abstract the UI part of your component into one or more pure functional components which your container renders by passing the data it got as props.
I believe the presentational/container component split is the pattern you're looking for here.
I am trying to use Enzyme's shallow wrapper to get the instance of my component and calling my class function over it. It shows me this error:
TypeError: tree.instance(...).onCampaignSelected is not a function
class ToolbarPage extends Component {
constructor(){
super();
this.onCampaignSelected = this.onCampaignSelected.bind(this);
this.state = {
item: null
}
}
onCampaignSelected (item) {
this.setState({item})
}
render () {
return (
<MyComponent onItemSelected={this.onCampaignSelected} />
)
}
}
function mapStateToProps(state){
buttons: state.toolbar.buttons
}
export default connect(mapStateToProps)(ToolbarPage);
My test case looks like this
import { shallow, mount } from 'enzyme';
import ToolbarPage from './ToolbarPage';
import configureStore from 'configureStore';
const store = configureStore();
const props = {
store,
isLoggedIn: false,
messageCounter: 0
}
describe('<ToolbarPage />', () => {
it('allows to select campaign', () => {
const tree = shallow(<ToolbarPage {...props}/>);
tree.instance().onCampaignSelected();
})
})
I also figured out that it is a wrapped component, so I won't get this function on the wrapped component. How do I access this function?
shallow does not render the full set of components with all of their properties & methods. It is intended for basic "did this thing render what I expected?" testing.
mount will give you everything and should allow you to test whatever you need. It is very useful for testing event handling & manipulating the state of components to test the interactions between components.
I have a component SampleComponent that mounts another "connected component" (i.e. container). When I try to test SampleComponent by mounting (since I need the componentDidMount), I get the error:
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ContainerComponent)". Either wrap the root component
in a , or explicitly pass "store" as a prop to
"Connect(ContainerComponent)".
What's the best way of testing this?
Enzyme's mount takes optional parameters. The two that are necessary for what you need are
options.context: (Object [optional]): Context to be passed into the component
options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper
You would mount SampleComponent with an options object like so:
const store = {
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ... whatever state you need to pass in ... })
}
const options = {
context: { store },
childContextTypes: { store: React.PropTypes.object.isRequired }
}
const _wrapper = mount(<SampleComponent {...defaultProps} />, options)
Now your SampleComponent will pass the context you provided down to the connected component.
What I essentially did was bring in my redux store (and Provider) and wrapped it in a utility component as follows:
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
then, I mount the SampleComponent and run tests against it:
it('contains <ChildComponent/> Component', () => {
const wrapper = mount(
<CustomProvider>
<SampleComponent {...defaultProps} />
</CustomProvider>
);
expect(wrapper.find(ChildComponent)).to.have.length(1);
});
Option 1)
You can wrap the container component with React-Redux's Provider component within your test. So with this approach, you actually reference the store, pass it to the Provider, and compose your component under test inside. The advantage of this approach is you can actually create a custom store for the test. This approach is useful if you want to test the Redux-related portions of your component.
Option 2)
Maybe you don't care about testing the Redux-related pieces. If you're merely interested in testing the component's rendering and local state-related behaviors, you can simply add a named export for the unconnected plain version of your component. And just to clarify when you add the "export" keyword to your class basically you are saying that now the class could be imported in 2 ways either with curly braces {} or not. example:
export class MyComponent extends React.Component{ render(){ ... }}
...
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
later on your test file:
import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import {MyComponent} from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.
I hope helps anyone out there.
There is also the option to use redux-mock-store.
A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.
The mock store provides the necessary methods on the store object which are required for Redux.
You can specify optional middlewares and your app specific initial state.
import configureStore from 'redux-mock-store'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {}
const store = mockStore(initialState)
const wrapper = mount(<SampleComponent store={store}/>)
You can use name export to solve this problem:
You should have:
class SampleComponent extends React.Component{
...
render(){
<div></div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)
You can add a export before class:
export class SampleComponent extends React.Component{
and import this component with no redux store:
import { SampleComponent } from 'your-path/SampleComponent';
With this solution you don't need to import store to your test files.
in an attempt to make the use of decorator syntax more testable I made this:
https://www.npmjs.com/package/babel-plugin-undecorate
input:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
output:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
export class __undecorated__AnyOldClass {
method() {
console.log('hello');
}
}
Hopefully this can provide a solid Option 3!