How do I test React-Responsive components with Enzyme? - reactjs

I am using the library React-Reponsive.
https://github.com/contra/react-responsive
I am struggling to figure out how to test components that are nested in React-Responsive Media Query Components:
export default class AppContainer extends React.Component {
render(){
return(
<MediaQuery minDeviceWidth={750}>
<Header />
</MediaQuery>
);
}
}
-
describe("AppContainer", () => {
let App;
let wrapper;
beforeEach(() => {
wrapper = mount(<Provider store={store}><AppContainer location={location} /></Provider>);
App = wrapper.find(AppContainer);
});
it('to have a <Header /> component', () => {
console.log(App.debug());
expect(App.find(Header)).to.have.length(1);
});
}
The test result:
1) AppContainer to have a <Header /> component:
AssertionError: expected { Object (component, root, ...) } to have a length of 1 but got 0
The relevant part of the output of the console.log is:
<MediaQuery minDeviceWidth={750} values={{...}} />
Indicating that Header is indeed not appearing in the render tree. However if I remove the MediaQuery and make Header a direct child of AppContainer the test passes.
I imagine this is not a bug as I'm very new to Enzyme and testing components in general. Any help or examples would be appreciated.
Please note: The other tests I have on this component are passing fine. I am confident that the imports and setup are all correct.

Issue was that Media Query is looking for window.matchMedia which with jsdom is undefined.
In this case I needed to use the Server Side Rendering implementation. However this would require a static value for width, which breaks the responsiveness.
My solution is to set a global variable on the test virtual DOM.
window.testMediaQueryValues = {width:740};
Then MediaQuery can access them if they are there:
<MediaQuery maxWidth={smallViewMaxWidth} values={window.testMediaQueryValues}>
In the case when the variable is not set, the null values are ignored and the Component renders as usual.
Big thanks to #Contra for his help and super library

What worked for me was adding a mocked react-responsive component using a __mocks__ directory. Basically create the following file in the directory structure:
-your-component
--component-using-media-query.js
--__mocks__
---react-responsive.js
Then you can mock out the MediaQuery component in the react-responsive.js file.
const MediaQuery = ({ children }) => children;
export default MediaQuery;

I was able to make it work using react-responsive v7.0.0.
Given:
<MediaQuery minDeviceWidth={750}>
<Header />
</MediaQuery>
The following works:
import { Context as ResponsiveContext } from 'react-responsive'
import { mount } from 'enzyme'
const wrappingComponent = ResponsiveContext.Provider
const wrappingComponentProps = { value: { width: 750 } }
const mountProps = { wrappingComponent, wrappingComponentProps }
const wrapper = mount(<AppContainer />, mountProps)

Try mocking the react-responsive dependency in your unit test. Working with Webpack, I'm using inject-loader for injecting test dependencies to modules. You can import your component using "inject-loader" and pass it which dependencies you want to overwrite.
Example:
import YourComponentInjector from 'inject-loader!../YourComponent.jsx';
and then
class MediaQueryDesktopMock extends React.Component {
render() {
const {minDeviceWidth, children} = this.props;
if(minDeviceWidth === 765) {
return (<div>{children}</div>)
}
return <span/>;
}
}
let YourComponentMock = YourComponentInjector({
'react-responsive': MediaQueryDesktopMock
});
you can then test for specific media queries

Related

Jest snapshot test adds a "_class" word into React HOC in snapshot on CI but not on my machine

I have a React component importing another React HOC.
My React HOC:
import { withRouter } from "react-router";
...
const InnerComponent: FC<any> = (props): ReactElement => {
...
}
const withData = compose(
graphql(SOME_QUERY, {
props: mapProps,
options,
}),
injectIntl
);
export default withRouter(withX(withData(InnerComponent)));
My React Component:
export const OuterComponent: FC<any> = (props): ReactElement => {
...
return (
...
<InnerComponent />
...
)}
This is an excerpt from my test spec file (I use enzyme & jest):
const component = shallow(<OuterComponent {...props} />)
expect(component).toMatchSnapshot()
Locally, everything works fine. The part where my InnerComponent should be rendered is represented like this in the snapshot:
<withRouter() />
When I check-in my code and let it run on CI, snapshot test fails because that line in the snapshot CI generates looks like this:
<withRouter(_class) />
I am sure that enzyme and jest versions are same on CI and my machine. I even tried to update jest and enzyme on my machine but never got a snapshot having this weird _class word.
Where does this _class come from and how can I avoid it, or how can make sure that my local setup also generates it during snapshot creation?
This problem can be avoided by setting proper displayName for our custom HOCs. In this case, the withX HOC didn't have any displayName. I changed it to look like below so that the HOC in snapshot is generated with same name both in local machine and in build server.
export function withX(WrappedComponent) {
class WithX extends React.Component {
render() {
...
return <WrappedComponent {...this.props} />
}
}
WithX.displayName = `WithX(${getDisplayName(WrappedComponent)})`
return WithX
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
This still doesn't explain why the HOC had no name in local machine but had the name _class on build server, though.

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 components that have JSS injected styles with enzyme

I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!
here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example
It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.

Shallow rendering using enzyme simple test not working

I am very new to the enzyme/shallow render testing. I probably don't fully understand it yet.
Using this simplified component:
export const PlacementOption = (props) => <div/>
const UpdatingSelectField = (props) => <div/>
export class DevicePlatforms extends Component{
render(){
return <div>
<UpdatingSelectField/>
{this.props.value.device_platforms && this.props.children}
</div>
}
}
I am trying to tests DevicePlatforms. If this.props.value.device_platforms is not present children are not rendered and if it is they are rendered.
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import { DevicePlatforms } from './Placement.js';
import { PlacementOption } from './Placement.js'
describe("<DevicePlatforms/>", function() {
it("with all devices selected renders all children", function() {
const value = {
device_platforms: "mobile/desktop",
}
const Component = <DevicePlatforms
value={value}
>
<PlacementOption/>
<PlacementOption/>
</DevicePlatforms>
const wrapper = shallow(Component)
expect(wrapper.find('PlacementOption')).toBe(2)
})
it("with no devices selected renders no children", function() {
const value = {}
const Component = <DevicePlatforms
value={value}
>
<PlacementOption/>
<PlacementOption/>
</DevicePlatforms>
const wrapper = shallow(Component)
expect(wrapper.find('PlacementOption')).toBe(0)
})
})
I have tried 'PlacementOption', PlacementOption in a find call.
All I get is a:
Expected ShallowWrapper({ many many lines of shallow wrapper content }) to be 3
Expected ShallowWrapper({ many many lines of shallow wrapper content }) to be 0
errors.
I can paste the "many many lines of shallow wrapper content" if needed but i don't think it is related and my problem is somewhere else - probably around somewhere around me not knowing how to use shallow render stuff.
You're asserting that an enzyme ShallowWrapper is equal to 3 or 0. This doesn't make sense.
Instead, the ShallowWrapper that is returned from .find() has a .length property. Try using that instead.
expect(wrapper.find('PlacementOption').length).toBe(2)

Injecting react-intl object into mounted Enzyme components for testing

EDIT: Solved! Scroll down for the answer
In our Component tests we need them to have access to the react-intl context. The problem is that we are mounting single components (with Enzyme's mount()) without their <IntlProvider /> parent wrapper. This is solved by wrapping the provider around but then the root points to the IntlProvider instance and not to CustomComponent.
The Testing with React-Intl: Enzyme docs are still empty.
<CustomComponent />
class CustomComponent extends Component {
state = {
foo: 'bar'
}
render() {
return (
<div>
<FormattedMessage id="world.hello" defaultMessage="Hello World!" />
</div>
);
}
}
Standard Test Case (Desired) (Enzyme + Mocha + Chai)
// This is how we mount components normally with Enzyme
const wrapper = mount(
<CustomComponent
params={params}
/>
);
expect( wrapper.state('foo') ).to.equal('bar');
However, since our component uses FormattedMessage as part of the react-intl library, we get this error when running the above code:
Uncaught Invariant Violation: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.
Wrapping it with IntlProvider
const wrapper = mount(
<IntlProvider locale="en">
<CustomComponent
params={params}
/>
</IntlProvider>
);
This provides CustomComponent with the intl context it asks for. However, when trying to do test assertions such as these:
expect( wrapper.state('foo') ).to.equal('bar');
raises the following exception:
AssertionError: expected undefined to equal ''
This ofcourse because it tries to read the state of IntlProvider and not our CustomComponent.
Attempts to access CustomComponent
I have tried the below to no avail:
const wrapper = mount(
<IntlProvider locale="en">
<CustomComponent
params={params}
/>
</IntlProvider>
);
// Below cases have all individually been tried to call `.state('foo')` on:
// expect( component.state('foo') ).to.equal('bar');
const component = wrapper.childAt(0);
> Error: ReactWrapper::state() can only be called on the root
const component = wrapper.children();
> Error: ReactWrapper::state() can only be called on the root
const component = wrapper.children();
component.root = component;
> TypeError: Cannot read property 'getInstance' of null
The question is: How can we mount CustomComponent with the intl context while still being able to perform "root" operations on our CustomComponent?
I have created a helper functions to patch the existing Enzyme mount() and shallow() function. We are now using these helper methods in all our tests where we use React Intl components.
You can find the gist here: https://gist.github.com/mirague/c05f4da0d781a9b339b501f1d5d33c37
For the sake of keeping data accessible, here's the code in a nutshell:
helpers/intl-test.js
/**
* Components using the react-intl module require access to the intl context.
* This is not available when mounting single components in Enzyme.
* These helper functions aim to address that and wrap a valid,
* English-locale intl context around them.
*/
import React from 'react';
import { IntlProvider, intlShape } from 'react-intl';
import { mount, shallow } from 'enzyme';
const messages = require('../locales/en'); // en.json
const intlProvider = new IntlProvider({ locale: 'en', messages }, {});
const { intl } = intlProvider.getChildContext();
/**
* When using React-Intl `injectIntl` on components, props.intl is required.
*/
function nodeWithIntlProp(node) {
return React.cloneElement(node, { intl });
}
export default {
shallowWithIntl(node) {
return shallow(nodeWithIntlProp(node), { context: { intl } });
},
mountWithIntl(node) {
return mount(nodeWithIntlProp(node), {
context: { intl },
childContextTypes: { intl: intlShape }
});
}
};
CustomComponent
class CustomComponent extends Component {
state = {
foo: 'bar'
}
render() {
return (
<div>
<FormattedMessage id="world.hello" defaultMessage="Hello World!" />
</div>
);
}
}
CustomComponentTest.js
import { mountWithIntl } from 'helpers/intl-test';
const wrapper = mountWithIntl(
<CustomComponent />
);
expect(wrapper.state('foo')).to.equal('bar'); // OK
expect(wrapper.text()).to.equal('Hello World!'); // OK

Resources