I am trying to test a connected react component, however once wrapping it, I cannot get the instance of it using instance(), it returns null. for non-connected components it does return the instance, what is the difference and how can i get an instance of a connected component?
it('connected component', () => {
const wrapper = mount(
<Provider store={store}>
<BrowserRouter>
<ConnectedComponent />
</BrowserRouter>
</Provider>
)
const myComp = wrapper.find(ConnectedComponent)
expect(myComp).toHaveLength(1) // passes
console.log(myComp.instance()) //null
})
it('non-connected component', () => {
const wrapper = mount(
<Provider store={store}>
<BrowserRouter>
<NonConnectedComponent />
</BrowserRouter>
</Provider>
)
const myComp = wrapper.find(NonConnectedComponent)
expect(myComp).toHaveLength(1) // passes
console.log(myComp.instance()) // prints the instancce
})
The issue is that for connected components you export the Connect wrapper, not the component itself. There are a few options to deal with it.
Option 1. Use dive(). Note that this is available only when you use shallow rendering and will not be available on mount.
const component = shallow(<ConnectedComponent />).dive(); // dive one level into the Connect wrapper
component.instance(); // the instance will be available
Option 2. Export your component separately before connecting it and use named import to get it.
// in your component
export class MyComponent extends React.Component {
...
}
export default connect()(MyComponent);
// in your tests
import { MyComponent } from './component'; // this will get you non-default export of the component, which is not connected
for me this worked
const wrapper = shallow(<ConnectedComponent />).childAt(0).dive();
console.log(wrapper.instance());
When I use this code
const wrapper = shallow(<ConnectedComponent />).childAt(0).dive();
console.log(wrapper.instance());
I am getting this error:
TypeError: ShallowWrapper::dive() can not be called on Host Components
Related
I am trying to test a specific component that is nested within two other components:
<Router>
<Provider store={store}>
<Howitworks />
</Provider>
</Router>
However when I try to run my test:
test("should call onTaskerClick", () => {
const spy = jest.spyOn(Howitworks.prototype, "onTaskerClick");
const wrapper = mount(
<Router>
<Provider store={store}>
<Howitworks />
</Provider>
</Router>
);
wrapper.find("#pills-tasker-tab.nav-link.tasklink").at(0).simulate("click");
expect(spy).toHaveBeenCalled();
});
I get a "spyOn on a primitive value; undefined given" error. I have tried different variations of mocking this "onTaskerClick" function when simulating a click on the link that invokes it but always get a variation of the error that the function is undefined or function was not called.
The link that invokes onTaskerClick:
<a className='nav-link tasklink' id='pills-tasker-tab' data-toggle='pill' onClick={this.onTaskerClick} role='tab' aria-controls='pills-tasker' aria-selected='false'>LINK<span></span></a>
Here is how the Howitworks component is currently being exported:
export default connect(mapStateToProps, { onNotifyClick })(Howitworks);
Unfortunately there's very limited documentation on accessing functions within a nested component in a test environment so any help would be great.
EDIT:
Updated test suite:
test("should call onTaskerClick", () => {
const wrapper = mount(
<Router>
<Provider store={store}>
<Howitworks />
</Provider>
</Router>
);
const spy =
jest.spyOn(wrapper.find(Howitworks.WrappedComponent).instance(), "onTaskerClick");
wrapper.find("#pills-tasker-tab.nav-link.tasklink").at(0).simulate("click");
expect(spy).toHaveBeenCalled();
});
mapStateToProps function and export:
let mapStateToProps = (state) => ({});
export default connect(mapStateToProps, { onNotifyClick })(Howitworks);
connect returns a higher-order component that doesn't inherit from original component, which is common in React.
In order for onTaskerClick to be spyable on a prototype, it should be prototype and not instance (arrow) method. Original component class can be either separate named export:
export class Howitworks {
onTaskerClick() {...}
...
}
export default connect(...)(Howitworks);
Or it can be accessed on connected component as WrappedComponent property.
Since onTaskerClick isn't called on component instantiation, there's no need to spy on a prototype, it's easier to do this on an instance, this doesn't limit a spy to prototype methods:
const wrapper = mount(...);
const origComp = wrapper.find(Howitworks.WrappedComponent).instance();
const spy = jest.spyOn(origComp, "onTaskerClick");
origComp.forceUpdate();
// trigger click
I'm trying to run my first jest test but it seems like there's an issue with the way my react files are set up:
app_test:
it('renders without crashing', () => {
const app = shallow(<App />);
});
app.js
class App extends Component {
componentWillMount() {
this.fetchUserAccountInfo();
}
render() {
return <MainRoutes auth={this.props.loggedIn} />;
}
}
function mapStateToProps(state) {
return {
loggedIn: state.loggedIn.userLoggedIn,
};
}
export default connect(
mapStateToProps,
{ fetchUserAccountInfo }
)(App);
index.js (embeds App.s)
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
const token = localStorage.getItem("token");
const bugsnagClient = bugsnag({...//bugsnag stuff})
bugsnagClient.use(bugsnagReact, React);
const ErrorBoundary = bugsnagClient.getPlugin("react");
const RootApp = () => (
<ErrorBoundary>
<Provider store={store}>
<App id={token} />
</Provider>
</ErrorBoundary>,
);
ReactDOM.render(<RootApp />, document.getElementById('.app'));
They say i have an issue with shallow rendering "App"
Invariant Violation: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)".
I'm not sure what else I need to pass through App or if I need to move the Provider down?
The error log clearly states here that you need to provide a store to your component. To do so, you need to mock your store beforehand, and then give it to the <Provider /> component, containing the <App /> as child. Your file should look like this :
/* app_test */
import React from 'react';
import { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import App from './App';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
it('renders without crashing', () => {
const store = mockStore({}); // Instead of {}, you can give your initial store
shallow(
<Provider store={store}> // Provides the store to your App
<App />
</Provider>
);
});
This piece of code should do the trick !
For more info, you can check this page: https://redux.js.org/recipes/writing-tests
I have some configuration settings I want to set and save to redux store before my main app loads. This is so that all the children components already have the configuration data pre-loaded and they can read it from the store using redux-connect.
For now, I only know how to use and update the store inside my connected components that are wrapped in the Provider context.
Is there a way to update it outside of that?
For example:
class App extends React {
constructor(props) {
super(props);
// UPDATE REDUX STORE HERE
}
componentDidUpdate() {
// OR UPDATE REDUX STORE HERE
}
render() {
return (
<Provider store={store}>
<Child />
</Provider>
);
}
}
Is this even possible?
store is just an object which contains dispatch and getState functions. This means that wherever the store is accessible outside of the application, you can manipulate it using these attributes.
const store = configureStore();
const App = () => (<Provider store={store} />);
store.dispatch({ type: SOME_ACTION_TYPE });
you can call the Provider outside of App Component in index.js
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('root'));
now in your App class it's possile add a connect or an updateAction
import {connect} from "react-redux";
import {updateStuf} from "./actions/projectActions";
class App extends Component {
componentWillMount() {
this.props.updateStuf();
}
render() {
const {stuff} = this.props;
return (
<div className="stuff">
</div>
);
}
}
const mapStateToProps = (state) => {
return {
stuff: state.project.stuff
}
}
export default connect(mapStateToProps, {updateStuf})(App);
I have a component defined as
class _MyComponent extends React.Component{
render() {
return (
...
);
}
}
const MyComponent= connect(
store => {
return {
...
};
}
)(_MyComponent);
export default MyComponent;
Now I want to insert it into a container (a div with id="container")
ReactDOM.render(<div>{React.createElement(MyComponent)}</div>, document.getElementById("container"))
I got
Uncaught Error: Could not find "store" in either the context or props of "Connect(_MyComponent)".
How can I pass the Redux store into the component "MyComponent"?
thanks
coolshare
You are missing the Provider that delivers the store via context. That is basically what the Error message is telling you.
Take a look at the documentation: https://github.com/reactjs/react-redux/blob/master/docs/api.md#vanilla-react
ReactDOM.render(
<Provider store={store}>
<MyRootComponent />
</Provider>,
rootEl
)
So all you have to do is wrap MyComponent with the Provider. The Provider needs the store. It is also your responsibility to create that store.
http://redux.js.org/docs/api/Store.html#example
The standard way as per the documentation is by wrapping _MyComponent inside a Provider like this:
import { Provider } from 'react-redux';
import { createStore } from 'redux';
const store = createStore(/* reducers here */);
// .._MyComponent definition
// Render to the DOM
ReactDOM.render(
<Provider store={store}>
<_MyComponent />
</Provider>
, document.getElementById("container"))
You can then access the store by using the connect method from react-redux.
I have a test which is setting props, to observe some changes in the component. The only complication is that I'm wrapping the rendered element in a <Provider> because there are some connected components further down the tree.
I'm rendering via
const el = () => <MyComponent prop1={ prop1 } />;
const wrapper = mount(<Provider store={store}>{ el() }</Provider>);
I'm then trying to observe some changes by using the following:
wrapper.setProps({ /* new props */ });
// expect()s etc.
The problem is that setProps() is not setting the props properly on the wrapped component. I assume that this is because <Provider> is not actually passing props through as it's not an HoC. Is there a better way to test this than just changing the locally scoped prop variables and re-rendering?
Here's an approach using the setProps
import { Provider } from 'react-redux';
const renderComponent = properties => mount(
React.createElement(
props => (
<Provider store={store}>
<IntlProvider locale="en" defaultLocale="en" messages={messages}>
<Router>
<MyComponent {...props} />
</Router>
</Provider>
),
properties))
Then in your test
it('should set some props', () => {
const renderedComponent = renderComponent(props);
renderedComponent.setProps({ /* some props */ } });
expect(true).toBe(true);
})
Another approach will be to use wrappingComponent.
For example, let say this is your provider
const ExampleProvider = ({ children }) => (
<Provider store={store}>
{children}
</Provider>
);
and then initialize wrapper as follows
wrapper = mount(<Component />, { wrappingComponent: ExampleProvider});
And then in test cases, you should be able to call wrapper.setProps directly.
You should only be calling setProps on the wrapped component or parent.
A good rule of thumb is your test should only be testing a single component (the parent), so setting props on children is not permitted with enzyme.
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/setProps.md#setpropsnextprops--self
If you have child components further down that we need to satisfy store dependencies for (via Provider and context), then that is fine, but those child components should really have their own isolated tests.
That is where you'd be encouraged to write a test for a change on setProps.
If you find yourself writing tests for a container or connector, you would really only want to verify that the child component is receiving the correct props and or state. For example:
import { createMockStore } from 'mocks'
import { shallwo } from 'enzyme'
// this component exports the redux connection
import Container from '../container'
const state = { foo: 'bar' }
let wrapper
let wrapped
let store
beforeEach(() => {
store = createMockStore(state)
wrapper = shallow(<Container store={store} />)
wrapped = wrapper.find('MyChildComponent')
})
it('props.foo', () => {
const expected = 'bar'
const actual = wrapped.prop('foo')
expect(expected).toEqual(actual)
})
One other tip is that it helps to understand the difference between shallow and mount so you aren't needlessly mocking dependencies for children in a test, the top answer here is a good read:
When should you use render and shallow in Enzyme / React tests?
I had the same issue after wrapping my component in a Provider. What I did is, I used setProps on children instead of a component itself.
This is the example of my component in test suite:
let component, connectedComponent;
component = () => {
store = configureMockStore()(myStore);
connectedComponent = mount(
<Provider >
<MyComponent store={store} params={{xyz: 123}} />
</Provider>);};
But in the test itself, I did this:
connectedComponent.setProps({children: <MyComponent params={{}} />});
I would like to provide a similar solution to the one above.
With the mount wrapper as defined in the question,
const wrapper = mount(<Provider store={store}>{ el() }</Provider>);
And then, on the test itself, you can simply clone the children, which is the child component within Provider, and then call setProps:
it('should get do x and y', () => {
wrapper.setProps({
children: cloneElement(wrapper.props().children as ReactElement, { ...props }),
});
// handle the rest below
});
If you are using JavaScript, there is no need to include the as ReactElement type assertion.
For those who are using TypeScript, it is necessary to assert it as ReactElement, as ReactChild could be of types ReactElement<any> | ReactText. Since we are certain that the element rendered within Provider is a ReactElement, the fastest solution would be to use type assertion.