Testing with Jasmine and Enzyme and I've been trying to test a HOC which is connected to redux. Take for instance the following HOC:
import React, { Component } from 'react';
import { connect } from 'react-redux';
const WithAreas = (WrappedComponent) => {
class WithAreasComponent extends Component {
constructor(props) {
super(props);
}
shouldRenderWrappedComponent(userObj) {
return !!userObj.areas;
}
render() {
const { userObj } = this.props;
return shouldRenderWrappedComponent(userObj)
? <WrappedComponent {...this.props} />
: null;
}
}
function mapStateToProps(state) {
const { info } = state;
const { userObj } = info.userObj;
return { userObj };
}
return connect(mapStateToProps)(WithAreasComponent);
};
export default WithAreas;
Let's say I want to test this HOC in order to check if the wrapped component is being render according to the userObj. I thought about doing a mock component and pass it to the HOC, but this is not working.
Test.js File:
import React from 'react';
import { shallow } from 'enzyme';
import jasmineEnzyme from 'jasmine-enzyme';
import WithAreas from './';
class MockComponent extends React.Component {
render() {
return(
<div> MOCK </div>
);
}
}
function setup(extraProps) {
const props = {
info: {
userObj: {
id: 'example1'
}
},
};
Object.assign(props, extraProps);
const WithAreasInstance = WithAreas(MockComponent);
const wrapper = shallow(<WithAreasInstance {...props} />);
return {
props,
wrapper
};
}
fdescribe('<WithAreas />', () => {
beforeEach(() => {
jasmineEnzyme();
});
it('should render the Mock Component', () => {
const { wrapper } = setup();
expect(wrapper.find(MockComponent).exists()).toBe(true);
});
});
But it gives me this error:
TypeError: (0 , _.WithAreas) is not a function
at setup (webpack:///src/user/containers/WithAreas/test.js:20:47 <- src/test_index.js:9:18060087)
at UserContext.<anonymous> (webpack:///src/user/containers//WithAreas/test.js:34:24 <- src/test_index.js:9:18060822)
What am I doing wrong? Or what approach might you recommend?
Thanks for any given help.
You're not importing the HOC in test correctly. You've set it as the default export, but are using named export deconstruction.
Try this in your test file:
import WithAreas from './';
Also, don't forget to pass the store to your component in test, otherwise the connect HOC won't work as expected.
shallow(
<Provider store={store}>
<WithAreasInstance ...>
</Provider>)
Related
I've been working around with unit tests using jest in React and HOC. Currently, I am facing an issue in accessing the state and method of my class. Check the sample code below
//Login.Container.js
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
isShowLoader: false,
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit() {
//sample code
}
render() {
return (
<LoginComponent />
);
}
function mapStateToProps(state) {
return {
task: state
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
Object.assign({},
actions),
dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login));
Login.displayName = 'Login';
Sample login component
//Login.Component.js
const LoginComponent = props => {
return (<div>hi</div>);
}
My sample test suite using jest and enzyme
//login.test.js
import { Provider } from 'react-redux';
import { history, store} from '../store';
import { ConnectedRouter } from 'react-router-redux';
describe('>>>Login --- Container', () => {
let wrapperInner
it('should perform login container by using ComponentWapper', async () => {
wrapperInner = mount(<Provider store={store}>
<ConnectedRouter history={history}>
<Login />
</ConnectedRouter>
</Provider>);
const instance = wrapperInner.instance();
expect(wrapperInner.state('isShowLoader')).toBe(true);
const responseJson = await instance.handleSubmit();
});
});
Finally, I found the solution for this issue.
wrapperInner.find("Login").instance() // to access the methods
wrapperInner.find("Login").instance().state // to access the state
For more details check here: https://github.com/airbnb/enzyme/issues/361
So i haven't been able to get the todos passed down from the parent component nor have i been able to successfully set them myself. I'm not trying to test anything in particular at this point. Just that the component 'exists' without error
Component code:
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class TodoList extends Component {
renderTodos = () => {
const { todos } = this.props;
return todos.map(todo => {
return <TodoItem key={todo.id} {...todo} />;
});
};
render() {
return (
<div>
{this.renderTodos()}
</div>
);
}
}
export default TodoList;
Test code:
import React from 'react';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
import TodoList from './TodoList';
import TodoItem from './TodoItem';
describe(TodoList, () => {
const todos = [
{
id: 1,
text: 'Walk the walk'
},
{
id: 2,
text: 'Talk the talk'
}
];
const component = shallow(<TodoList todos={todos} />);
it('should exist', () => {
const component = renderer.create(<TodoList />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
Please help. New to Jest and pretty new to testing in general.
When you create a shallow instance of your component you need to pass the props which you are using without conditional check in your component
const component = shallow(<TodoList todos={[]}/>);
and
const component = renderer.create(<TodoList todos={[]}/>);
It seems like you can't test state in react components once you make them connected components. Anyone know why? To illustrate the point, I have a test for a react component that passes without redux and fails as soon as you make it connected.
// MyComponent.jsx
import React from 'react'
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar'
};
}
render() {
return <div></div>
}
}
export default MyComponent
Here is the passing test:
// MyComponent.test.js
import React from 'react'
import MyComponent from '../src/components/MyComponent'
import { mount } from 'enzyme'
describe('MyComponent', () => {
describe('interactions', () => {
let wrapper
beforeEach(() => {
wrapper = shallow(<MyComponent />)
})
it('foo to equal bar', () => {
expect(wrapper.state().foo).toEqual('bar')
})
})
})
Now I'm introducing Redux and connecting the component:
// MyComponent.jsx
...
export default connect(function (state){
return {
currentUser: state.currentUser
}
})(MyComponent);
And here is the updated test:
// MyComponent.test.js
import React from 'react'
import MyComponent from '../src/components/MyComponent'
import { mount } from 'enzyme'
import configureStore from 'redux-mock-store'
describe('MyComponent', () => {
const state = {}
const mockStore = configureStore()
describe('interactions', () => {
let wrapper
beforeEach(() => {
wrapper = mount(<MyComponent store={ mockStore(state) } />)
})
it('foo to equal bar', () => {
expect(wrapper.state().foo).toEqual('bar')
})
})
})
I would recommend just exporting both the connect() (the default export) and the actual component itself.
This way you can test the component separately outside of the connected version of the component.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar'
};
}
render() {
return <div></div>
}
}
export MyComponent
export default connect(function (state){
return {
currentUser: state.currentUser
}
})(MyComponent);
and then the test:
import { MyComponent } from '../src/components/MyComponent'
import { mount } from 'enzyme'
describe('MyComponent', () => {
describe('interactions', () => {
let wrapper
beforeEach(() => {
wrapper = shallow(<MyComponent />)
})
it('foo to equal bar', () => {
expect(wrapper.state().foo).toEqual('bar')
})
})
})
That's because connect() generates a wrapper component that manages the store interaction process. In your second snippet, <MyComponent> is now the component generated by connect(), not your original component. You would need to dig another level of nesting deeper in the render hierarchy to check on the contents of the real component's state.
I am trying to test a little React component - Template block statement.
But do I can to do that? Because I can't even log that block statement from renderResult() method.
Is it possible? Only One way I see. It's just test the end result via Enzume Shallow. But I just wonder - is it possible?
Thanks for any info.
import React, { PropTypes } from "react";
export function zeta() {
return "zeta";
}
export default class AppComponent extends React.Component {
some() {
return "some";
}
renderResult() {
let res;
if (process.env.NODE_ENV === "production") {
res = ( <main><h1>App Component PROD</h1>{ this.props.children }</main> );
} else {
res = ( <main><h1>App Component DEV 11</h1>{ this.props.children }</main> );
}
return res;
}
render() {
return this.renderResult();
}
}
AppComponent.propTypes = {
children : PropTypes.object
};
My test
import jasmineEnzyme from "jasmine-enzyme";
import React from "react";
import {mount} from "enzyme";
import AppComponent, {zeta} from "../App.component";
describe("<AppComponent /> test", () => {
let wrapper;
beforeEach(() => {
jasmineEnzyme();
wrapper = mount(<AppComponent />);
});
it("App Component should contain h1", () => {
expect(wrapper.find("h1")).toBePresent();
});
it("h1 Should contain a dev text in case of dev mode", () => {
const compInst = wrapper.instance();
const res = compInst.some();
console.log(compInst.renderResult());
console.log(res);
expect(res).toMatch("some");
});
});
});
I've got fairly simple react component (Link wrapper which adds 'active' class if route is active):
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
const NavLink = (props, context) => {
const isActive = context.router.isActive(props.to, true);
const activeClass = isActive ? 'active' : '';
return (
<li className={activeClass}>
<Link {...props}>{props.children}</Link>
</li>
);
}
NavLink.contextTypes = {
router: PropTypes.object,
};
NavLink.propTypes = {
children: PropTypes.node,
to: PropTypes.string,
};
export default NavLink;
How am I supposed to test it? My only attempt was:
import NavLink from '../index';
import expect from 'expect';
import { mount } from 'enzyme';
import React from 'react';
describe('<NavLink />', () => {
it('should add active class', () => {
const renderedComponent = mount(<NavLink to="/home" />, { router: { pathname: '/home' } });
expect(renderedComponent.hasClass('active')).toEqual(true);
});
});
It doesn't work and returns TypeError: Cannot read property 'isActive' of undefined. It definitely needs some router mocking, but I have no idea how to write it.
Thanks #Elon Szopos for your answer but I manage to write something much more simple (following https://github.com/airbnb/enzyme/pull/62):
import NavLink from '../index';
import expect from 'expect';
import { shallow } from 'enzyme';
import React from 'react';
describe('<NavLink />', () => {
it('should add active class', () => {
const context = { router: { isActive: (a, b) => true } };
const renderedComponent = shallow(<NavLink to="/home" />, { context });
expect(renderedComponent.hasClass('active')).toEqual(true);
});
});
I have to change mount to shallow in order not to evaluate Link which gives me an error connected with the react-router TypeError: router.createHref is not a function.
I would rather have "real" react-router than just an object but I have no idea how to create it.
For react router v4 you can use a <MemoryRouter>. Example with AVA and Enzyme:
import React from 'react';
import PropTypes from 'prop-types';
import test from 'ava';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { MemoryRouter as Router } from 'react-router-dom';
const mountWithRouter = node => mount(<Router>{node}</Router>);
test('submits form directly', t => {
const onSubmit = sinon.spy();
const wrapper = mountWithRouter(<LogInForm onSubmit={onSubmit} />);
const form = wrapper.find('form');
form.simulate('submit');
t.true(onSubmit.calledOnce);
});
Testing components which rely on the context can be a little tricky. What I did was to write a wrapper that I used in my tests.
You can find the wrapper below:
import React, { PropTypes } from 'react'
export default class WithContext extends React.Component {
static propTypes = {
children: PropTypes.any,
context: PropTypes.object
}
validateChildren () {
if (this.props.children === undefined) {
throw new Error('No child components were passed into WithContext')
}
if (this.props.children.length > 1) {
throw new Error('You can only pass one child component into WithContext')
}
}
render () {
class WithContext extends React.Component {
getChildContext () {
return this.props.context
}
render () {
return this.props.children
}
}
const context = this.props.context
WithContext.childContextTypes = {}
for (let propertyName in context) {
WithContext.childContextTypes[propertyName] = PropTypes.any
}
this.validateChildren()
return (
<WithContext context={this.props.context}>
{this.props.children}
</WithContext>
)
}
}
Here you can see a sample usage:
<WithContext context={{ location: {pathname: '/Michael/Jackson/lives' }}}>
<MoonwalkComponent />
</WithContext>
<WithContext context={{ router: { isActive: true }}}>
<YourTestComponent />
</WithContext>
And it should work as you would expect.
You can use https://github.com/pshrmn/react-router-test-context for that exact purpose
"Create a pseudo context object that duplicates React Router's context.router structure. This is useful for shallow unit testing with Enzyme."
After installing it, you will be able to do something like
describe('my test', () => {
it('renders', () => {
const context = createRouterContext()
const wrapper = shallow(<MyComponent />, { context })
})
})