Reputation: 163
I'm learning how to test React/Redux components using enzyme. The component takes app-level state as props. When I run the test, I get the errors:
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components).
TypeError: Cannot read property 'contextTypes' of undefined
with my console.log of wrapper
in the below test file logging as undefined
.
I know that there's something wrong with my setup here, and have spent a couple hours trying to figure this out. Can anybody see anything obvious in the way that I'm importing and trying to use the component? I can't figure out why it is undefined
. Thanks in advance for any help or insight!
BackendDisplay.js
import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
var BackendDisplay = React.createClass({
render() {
const { username, node_version, app_path, timestamp } = this.props.loginState;
const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');
return (
<div>
<h1>Welcome, {username}!</h1>
<p><span className="bold">Node Version:</span> {node_version}</p>
<p><span className="bold">Application Path:</span> {app_path}</p>
<p><span className="bold">Date/Time:</span> {dateTime}</p>
</div>
);
}
});
const mapStateToProps = function(store) {
return store;
}
module.exports = connect(mapStateToProps)(BackendDisplay);
BackendDisplay.test.js
'use strict';
import React from 'react';
import {shallow} from 'enzyme';
import { connect } from 'react-redux';
import { BackendDisplay } from '../components/BackendDisplay';
describe('<BackendDisplay />', () => {
it('Correctly displays username, node_version, app_path, and timestamp', () => {
const wrapper = shallow(<BackendDisplay />);
console.log(wrapper);
});
});
Edited after changes: BackendDisplay.js
import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
var BackendDisplay = React.createClass({
render() {
const { username, node_version, app_path, timestamp } = this.props.loginState;
const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');
return (
<div>
<h1>Welcome, {username}!</h1>
<p><span className="bold">Node Version:</span> {node_version}</p>
<p><span className="bold">Application Path:</span> {app_path}</p>
<p><span className="bold">Date/Time:</span> {dateTime}</p>
</div>
);
}
});
const mapStateToProps = function(store) {
return store;
}
// module.exports = connect(mapStateToProps)(BackendDisplay);
export default connect(mapStateToProps)(BackendDisplay);
BackendDisplay.test.js
'use strict';
import React from 'react';
import {shallow} from 'enzyme';
import { connect } from 'react-redux';
import store from '../store';
import { Provider } from 'react-redux';
import ConnectedBackendDisplay, {BackendDisplay} from '../components/BackendDisplay';
describe('<BackendDisplay />', () => {
it('Correctly displays username, node_version, app_path, and timestamp', () => {
const wrapper = shallow(
<Provider store={store}>
<BackendDisplay />
</Provider>
);
console.log(wrapper.find(BackendDisplay));
expect(wrapper.find(BackendDisplay).length).to.equal(1);
});
});
Error message:
TypeError: Enzyme::Selector expects a string, object, or Component Constructor
Upvotes: 1
Views: 2625
Reputation: 7180
Your BackendDisplay is a container component and it is connected to the Redux store through the use of the connect api.
You should export the undecorated component for testing purposes. Since it is undecorated this exported component will not be wrapped with react-redux's Connect component.
var BackendDisplay = React.createClass({
render() {
const { username, node_version, app_path, timestamp } = this.props.loginState;
const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');
return (
<div>
<h1>Welcome, {username}!</h1>
<p><span className="bold">Node Version:</span> {node_version}</p>
<p><span className="bold">Application Path:</span> {app_path}</p>
<p><span className="bold">Date/Time:</span> {dateTime}</p>
</div>
);
}
});
Then you can import it as follows to make the test work
import {BackendDisplay} from 'BackendDisplay'
As a bonus you can also export the decorated BackendDisplay component by changing the following line
module.exports = connect(mapStateToProps)(BackendDisplay);
to
export default connect(mapStateToProps)(BackendDisplay);
This is how to import both the decorated and undecorated components
import ConnectedBackendDisplay, {BackendDisplay} from 'BackendDisplay'
ConnectedBackendDisplay refers to the decorated component which is exported through the unnamed export (export default BackendDisplay).
We just give it this name so that its clear it is wrapped in a connect component.
I have updated the following component to use export default which gives an unnamed export.
BackendDisplay
import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';
export const BackendDisplay = React.createClass({
render() {
const { username, node_version, app_path, timestamp } = this.props;
// removed reference to this.props.loginState
const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');
return (
<div>
<h1>Welcome, {username}!</h1>
<p><span className="bold">Node Version:</span> {node_version}</p>
<p><span className="bold">Application Path:</span> {app_path}</p>
<p><span className="bold">Date/Time:</span> {dateTime}</p>
</div>
);
}
});
const mapStateToProps = function(store) {
return store;
}
export default connect(mapStateToProps)(BackendDisplay);
Here is the test suite to demonstrate testing the above component both as a decorated and undecorated component with enzyme.
I am using the chai library to make test assertions easier. The jsdom library is also being used to create a DOM environment so we can test components using Enzyme's mount function which fully renders components.
test
'use strict';
import React from 'react';
import jsdom from 'jsdom'
import { expect } from 'chai'
import { shallow , mount} from 'enzyme';
import { Provider } from 'react-redux';
import ConnectedBackendDisplay, // decorated component
{BackendDisplay} from 'app/components/BackendDisplay'; // undecorated component
// for mocking a store to test the decorated component
import configureMockStore from 'redux-mock-store';
// create a fake DOM environment so that we can use Enzyme's mount to
// test decorated components
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView
describe.only('<BackendDisplay />', () => {
it('undecorated component correctly displays username', () => {
// define the prop we want to pass in
const username = 'Foo'
// render the component with the prop
const wrapper = mount(<BackendDisplay username={username} />);
// test that the text of the first <p> equals username prop that we passed in
expect(wrapper.find('h1').first().text()).to.equal(username);
});
it('decorated component correctly displays username', () => {
// define the prop we want to pass in
const username = 'Foo'
const initialState = { }
// create our mock store with an empyty initial state
const store = configureMockStore(initialState)
// render the component with the mockStore
const wrapper = shallow(<Provider store={store}>
<ConnectedBackendDisplay username={username}/>
</Provider>);
// test that the text of the first <p> equals username prop that we passed in
expect(wrapper.find('h1').first().text()).to.equal(username);
});
});
Upvotes: 4