using enzyme.mount().setProps with a react-redux Provider - reactjs

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.

Related

Unable to find functions or node when testing a nested component in Jest/Enzyme

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

enzyme wrapper.instance() is null for connected component

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

How test a React Loadable component

I have this component:
import React from 'react';
const UploadAsync = Loadable({
loader: () => import('components/Upload'),
loading: () => <LoadingComponent full />
});
const Component = () => {
return <UploadAsync />
}
export default Component
And the test:
import React from 'react';
import Component from './index';
describe('Component', () => {
it('should render correctly with upload component', () => {
const tree = create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
});
How I can see the Upload component and not the Loading component in the snapshot?
exports[`Content should render correctly with form component 1`] = `
<div>
<Loading
full={true}
/>
</div>
`;
So far I have tried setTimeOut and Promises.
Use Loadable.preloadAll() before the tests then you can access the Component you need.
Docs
Simple example:
all imports here
Loadable.preloadAll()
describe('TestName', () => {
//tests
})
I haven't been able to figure this out either, but here are a couple of workarounds that, alone or together, may work reasonably well:
Snapshot test the components that are passed to Loadable
In your case, the test would look something like this:
import React from 'react';
import Component from './components/Upload';
describe('Component', () => {
it('should render correctly with upload component', () => {
const tree = create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
});
You could also test <LoadingComponent full /> in a similar fashion. No, this doesn't assure you that the Loadable component is working, but you may find it satisfactory to assume that the react-loadable library is well tested and will work as long as you pass to it your own, properly tested components.
End-to-end browser testing
Using a framework such as Selenium or TestCafe you can write tests that run against your site as it runs in a real browser.
It seems like there is no proper solution but if your test is actually rendering the component in browser inside iframe then you can get your react component by Jquery contentDocument
$('#component-iframe')[0].contentDocument
you can find for some specific element in your component by class or id using
$('#component-iframe')[0].contentDocument.getElementById('componentID')
I have a solution, which I found accidentally, but I don't understand how it works (maybe if we could figure out, we could solve this problem). For now, it's good for a workaround.
If you call mount once outside the test, the dynamic module will load magically:
function mountApp() {
return mount(
<ApolloProvider client={apolloClient}>
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
</ApolloProvider>
);
}
mountApp();
// Tests
test('react-loadable module loads', () => {
const wrapper = mountApp();
console.log(wrapper.debug());
});
Here, the App, which contains react-loadable modules loads correctly with all its content available. When I remove the first mountApp, it doesn't work anymore (it loads only the loading string).
Edit:
Actually it works inside the test too, but this way you only need to do this once for every test to work.
I was trying to test a component which had Loadable-components inside of it, and run into a similar problem. I managed to mock those components and that made the parent-component mount (enzyme) as I wanted it to.
I'm leaving out the mocked store and props, as they aren't relevant to the solution
const component = () => {
return mount(
<Provider store={store}>
<DoubleMatrix {...props} />
</Provider>
)
}
// These are the Loadable-components, import { Grid, GridColumn } from 'App/Components/Tables/Grid' in the parent component which I am testing
jest.mock('App/Components/Tables/Grid', () => ({
Grid: () => <div />, // eslint-disable-line
GridColumn: () => <div />, // eslint-disable-line
}))
it('renders', () => {
const wrapper = component()
expect(wrapper).toMatchSnapshot()
})
Here is how you test the loaded component:
import {LoadableFoo} from './loadable-foo';
import {Foo} from './Foo';
it('should load the Foo component', async () => {
// usual shallow render
const component = shallow(<LoadableFoo/>);
// prerender the Loadable component
const loadedComponent = await component.preload();
expect(loadedComponent.Foo).toEqual(Foo);
});

Testing react component enclosed in withRouter (preferably using jest/enzyme)

I have a React component which is enclosed within Higher Order Component withRouter as below:
module.exports = withRouter(ManageProfilePage);
My routes are as below:
<Route path="/" component={AdrApp}>
<IndexRoute component={Login}/>
<Route component={CheckLoginStatus}>
<Route path="manage-profiles/:profileId" component=
{ManageProfilesPage}/>
</Route>
<Route path="*" component={notFoundPage}/>
</Route>
I need to use once of the Router lifecycle methods, that is why I need withRouter:
class ManageProfilePage extends React.Component {
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, () => {
...
})
render(){
...
}
}
I need to test this component using Jest/Enzyme and I wrote the test case as below:
describe('manage profile page test suite', () => {
it('snapshot test', () => {
const setRouteLeaveHook =jest.fn();
let wrapper = shallow(
<ManageProfilePage params={{id : 25, router:
setRouteLeaveHook}}/>
);
expect(wrapper).toMatchSnapshot();
})
})
The issue is it is not rendering one level deep. I am pasting the snapshot below:
exports[`manage drug term page test suites snapshot test 1`] = `
<ManageProfilePage
params={
Object {
"id": 25,
"router": [Function],
}
}
/>
`;
Is there any different way I can write my test case so that I am able to render ManageProfilePage atleast 1 level deep? It is not able to render as it is enclosed within WithRouter? How do we test these type of components?
Normally if we try to test such components we won’t be able to render it as it is wrapped within WithRouter (WithRouter is a wrapper over a component which provides
Router props like match, route and history to be directly used within the component).
module.exports = withRouter(ManageProfilePage);
To render such components, we have to explicitly tell it to render the wrapped component using WrappedComponent keyword.
For Eg. we will use below code for snapshot test:
describe('manage profile page test suite', () => {
it('snapshot test', () => {
const setRouteLeaveHook =jest.fn();
let wrapper = shallow(
<ManageProfilePage.WrappedComponent params={{id : 25, router:
setRouteLeaveHook}}/>
);
expect(wrapper).toMatchSnapshot();
})
})
This will tell enzyme to do shallow rendering (Shallow Rendering renders only that particular component and skips child components) for ManageProfilePage which is wrapped component within WithRouter.
Shallow rendering will only render one level, that's part of the specs for it.
you can use Mount which will render the whole tree, but I don't think you can limit how many levels deep it will render.
In any case, when using High Order Components I usually just export the base component as well(before wrapping it), this way I can do all my tests without the wrapper, and simply pass mocks for the required providers.
same thing with a Connect component with redux, you export your regular component and test the different props on that, instead of the connected one.
also note that some with... wrappers do not expose the inner instance(some do, but some don't) , so testing on your own component instead of the wrapper helps there as well.
I think you should try this
describe('manage profile page test suite', () => {
it('snapshot test', () => {
let wrapper = shallow(
<ManageProfilePage.WrappedComponent/>
);
expect(wrapper).toMatchSnapshot();
})
})
What I did which fixed the problem:
In "this.props.match.params.id" is used the post component
In the test case
const miniProps = {
otherProps,
match:{params:{id:'112'}}
};
const wrapper = shallow();

Testing nested react components with enzyme

This is a bit of a followup to Nested components testing with Enzyme inside of React & Redux ...
I have a component that I need to test props and methods on. However, that component needs to be rendered in a provider in order to work:
const renderComponent = (props) => {
<Provider store={ createStore(reducer, initialState) }>
<ChildComponent { ...props }/>
</Provider>
};
My question is, how do I test methods on this component now? This does not work:
it('can call its own methods', ()=> {
const wrapper = mount(renderComponent(defaultProps)).find('ChildComponent');
wrapper.instance().call('someMethod'); // returns Error: ReactWrapper::instance() can only be called on the root
});
I just google it and I find this link
http://paulsturgess.co.uk/blog/2016/11/06/how-to-write-and-test-a-redux-container-component-for-react/
Don't know if works.. I usually mock the store and don't use Provider...

Resources