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
Related
While learning higher order component in react, I was trying to use it in my code. While doing so, I am getting "Invariant Violation Error"
//Higher Order Component
const withStyles = (OriginalComponent) => {
class NewComponent extends React.Component{
render(){
return <OriginalComponent />
}
}
return NewComponent;
}
class FancyButton extends React.Component {
render() {
return <button>Fancy button</button>;
}
}
const App = props => {
//I am calling the HOC to get my updated component
let Enhanced = withStyles(<FancyButton />);
return (
<Enhanced />
);
};
ReactDOM.render(<App />, document.getElementById("root"));
Here I am calling the HOC(withStyles) and storing the returned component in a variable "Enhanced". In the App component, Can I directly call Enhanced component like ?
I expect the output should be button element, but I am getting "Invariant Violation" error
You are passing a rendered result of FancyButton, whereas you should've passed the actual component function
See below:
...
const App = props => {
//I am calling the HOC to get my updated component
let Enhanced = withStyles(FancyButton);
return (
<Enhanced />
);
};
...
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
I have been trying (without success) to write a test case for ErrorBoundary component that is handling errors via componentDidCatch lifecycle method.
Despite the error produced by child component inside the <ErrorBoundry> component, <ErrorBoundry> does not render info about error in code but the content of faulty component if it would work correct.
Component works as expected in production/development but not when it is executed by Jest / Enzyme for testing.
Error from testing:
PASS src/ErrorBoundary.test.js
● Console
console.error node_modules/fbjs/lib/warning.js:33
Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
in input (at ErrorBoundary.test.js:11)
in div (at ErrorBoundary.test.js:10)
in ComponentWithError (at ErrorBoundary.test.js:26)
in ErrorBoundry (created by WrapperComponent)
in WrapperComponent
console.log src/ErrorBoundary.test.js:29
<ErrorBoundry>
<ComponentWithError>
<div>
<input type="text" value={{...}} />
</div>
</ComponentWithError>
</ErrorBoundry>
ErrorBoundry.js:
import React, { Component } from 'react'
import Raven from 'raven-js'
import { Segment, Button } from 'semantic-ui-react'
export default class ErrorBoundry extends Component {
state = {
hasError: false
}
componentDidCatch(error, info) {
this.setState({ hasError: true })
Raven.captureException(error, { extra: info });
}
render() {
if(this.state.hasError) {
return (
<div className='error-boundry'>
<Segment>
<h2> Oh no! Somethin went wrong </h2>
<p>Our team has been notified, but click
<Button onClick={() => Raven.lastEventId() && Raven.showReportDialog()}>
here </Button> to fill out a report.
</p>
</Segment>
</div>
);
} else {
return this.props.children;
}
}
}
ErrorBoundry.test.js:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import renderer from 'react-test-renderer'
import { shallow, mount } from 'enzyme'
import ErrorBoundary from './ErrorBoundary'
class ComponentWithError extends Component {
render() {
return (
<div>
<input type = "text" value = {null}/>
</div>
);
}
}
describe('<ErrorBoundary> window',()=> {
it('should match the snapshot', () => {
const tree = renderer.create(<ErrorBoundary>Test</ErrorBoundary> ).toJSON()
expect(tree).toMatchSnapshot()
})
it('displays error message on error generated by child', () => {
const wrapper = mount(
<ErrorBoundary >
<ComponentWithError />
</ErrorBoundary>
)
console.log(wrapper.debug() )
})
})
Enzyme has simulateError helper now.
So this works very well for me:
const Something = () => null;
describe('ErrorBoundary', () => {
it('should display an ErrorMessage if wrapped component throws', () => {
const wrapper = mount(
<ErrorBoundary>
<Something />
</ErrorBoundary>
);
const error = new Error('test');
wrapper.find(Something).simulateError(error);
/* The rest fo your test */
}
}
After additional research I found that it is an open issue that has to be solved by Enzyme. https://github.com/airbnb/enzyme/issues/1255
I have implemented it as follows:
function ProblemChild() {
throw new Error('Error thrown from problem child');
return <div>Error</div>; // eslint-disable-line
}
describe('<ErrorBoundary> window',()=> {
it('displays error message on error generated by child', () => {
const spy = sinon.spy(ErrorBoundary.prototype, 'componentDidCatch')
mount(<ErrorBoundary><ProblemChild /></ErrorBoundary>)
chaiExpect(ErrorBoundary.prototype.componentDidCatch).to.have.property('callCount', 1)
})
})
Proposed workaround works anyhow
it is still not possible to test error message rendered to the app user by <ErrorBoundary>
test console displays warnings:
PASS src/ErrorBoundary.test.js
● Console
console.error node_modules/react-dom/cjs/react-dom.development.js:9627
The above error occurred in the <ProblemChild> component:
in ProblemChild (at ErrorBoundary.test.js:37)
in ErrorBoundry (created by WrapperComponent)
in WrapperComponent
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundry.
Adding to #Andreas Köberle's comment since the hasError state changes on ComponentDidCatch lifecycle method, you could also use enzymes setState.
You also don't need to mount the comment, shallow would do.
it('displays error message on error generated by child', () => {
const wrapper = shallow(
<ErrorBoundary >
<ComponentWithError />
</ErrorBoundary>
);
wrapper.setState({ hasError: true });
wrapper.toMatchSnapshot()
});
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.
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