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 })
})
})
Related
So I want to test a component that uses React's context api and it's not working. It should be seemingly pretty simple - as stated here (React js - How to mock Context when testing component) all you would have to do is something like this:
const context = { name: 'foo' };
const wrapper = mount(<SimpleComponent />, { context });
However, I have something very similar and it doesn't seem to be picking up on it.
Here is my test file -
import * as React from 'react'
import Enzyme, { shallow, mount } from "enzyme";
import Home from '../pages/home/index'
import Adapter from "enzyme-adapter-react-16";
import stateVals from '../__mocks__/stateVals';
console.log('value of stateVals: ', stateVals)
describe('Pages', () => {
describe('Index', () => {
it('should render without throwing an error', function () {
const wrap = mount(<Home/>, { stateData: { state: stateVals } })
expect(wrap.find('div').text()).toBe('hello there main home')
})
})
})
Here is my stateVals object I import:
const stateVals = {
ContextNext: "ldjkfs",
route: "/home",
styledcomponent: "sfsdfsdfsdf",
name: "dfasfasfasdf",
renderCloseAbout: false,
aboutCardStatus: "closed",
uploadedHistory: null,
greptchaTime: Date.now(),
loginStatus: "initial",
registrationStatus: "N/A",
userEmail: "N/A",
completeRegistration: {},
pageVal: "",
addPurchaseSubsJSON: ["empty"],
admins: ['SUPERDOOPERSECRET'],
modal: {
open: false,
message: ''
}
}
export default stateVals
And here is the beginning of the component I want to test -
class Home extends Component {
render(){
return(
<MainContext.Consumer>
{stateData => {
return(
<div className='grid-container abspos'>
{renderIf(stateData.state.modal.open==true)(
<Modal/>
)}
It throws this error:
TypeError: Cannot read property 'state' of undefined
26 | return(
27 | <div className='grid-container abspos'>
> 28 | {renderIf(stateData.state.modal.open==true)(
| ^
29 | <Modal/>
30 | )}
Why is this happening?
EDIT:
This also doesn't work:
import * as React from 'react'
import Enzyme, { shallow, mount } from "enzyme";
import Home from '../pages/home/index'
import Adapter from "enzyme-adapter-react-16";
import stateVals from '../__mocks__/stateVals';
console.log('value of stateVals: ', stateVals)
let context = {state: stateVals }
console.log('value of context: ', context)
describe('Pages', () => {
describe('Index', () => {
it('should render without throwing an error', function () {
const wrap = mount(<Home/>, context )
expect(wrap.find('div').text()).toBe('hello there main home')
})
})
})
Nor this;
const wrap = mount(<Home/>, {context: stateVals})
Nor using this:
const wrap = mount(<Home/>, {context: {stateData: stateVals}})
Nor this:
const wrap = mount(<Home/>, {context: {stateData: {state: stateVals}}})
Nor this;
const wrap = mount(<Home/>, {stateData: stateVals})
The answer turns out to be to wrap the provider as the context api hasn't been finalized yet. Thanks spenguin!
import * as React from 'react'
import Enzyme, { shallow, mount } from "enzyme";
import Home from '../pages/home/index'
import Adapter from "enzyme-adapter-react-16";
import { Provider } from '../services/Context/Provider'
import stateVals from '../__mocks__/stateVals';
describe('Pages', () => {
describe('Index', () => {
it('test ', function () {
const wrap = mount(<Provider value={{stateVals}}><Home/></Provider>)
expect(wrap.find('#maintext').text()).toBe('hello there main home')
})
})
})
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>)
I have a component that extends a generic component (custom react component). How do I unit-test using enzyme's mount? Here's the code:
Container.js
import React from 'react';
import request from 'API/request';
export default class Container extends React.Component {
componentDidMount() {
this.populateData();
}
componentDidUpdate() {
if (this.isDataStale()) {
return this.populateData();
}
}
populateData() {
const url = this.getUrl();
const params = this.getParams();
return request(this.props.dispatch, {
url,
params,
method: 'GET'
})
.then(response => {
this.dispatchData(response.data);
})
.catch(error => {
this.dispatchError(error);
});
}
render() {
if (this.getData() && !this.isDataStale()) {
return this.renderChildren();
} else {
return null;
}
}
getError() {}
getParams() {}
isDataStale() {
return false;
}
}
Here's the functional component:
import React from 'react';
import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom';
import CustomPage from 'Client/customPage';
import Container from 'Client/container';
import TestPanel from 'Client/testPanel/TestPanel';
import Greeting from 'Client/greeting';
import Button from '../Button';
export class Test extends Container {
constructor({ match }) {
super();
this.state = {
match: match,
id: match.params.id,
location: ''
};
}
isDataStale() {
return (
this.props.data.id !== this.props.match.params.id
);
}
getData() {
return this.props.data.values;
}
dispatchData(data) {
this.props.dispatch({
type: 'DATA_FOUND',
data: data,
id: this.state.id
});
}
dispatchError(error) {
this.props.dispatch({ type: 'CUSTOM_ERROR', data: error });
}
showDetails(url) {
this.props.history.push(url);
}
renderChildren() {
const match = this.state.match;
const testUrl = `/test/values/${this.state.id}`;
const verifyUrl = `${testUrl}/verify`;
return (
<div className="test-plan">
<Route path={verifyUrl} render={() => (
<Greeting show={true} />
)} />
<TestPanel
data={this.props.data}
id={this.props.match.params.id}
/>
<Route exact path={testUrl} render={() => (
<CustomPage id={this.state.id} />
)} />
<Route path={verifyUrl} render={() => (
<div className="next-button" ><Button label = {' NEXT '} onButtonClick = { this.showDetails.bind(this, loanUrl) } /> </div>
)} />
</div>
);
}
}
const mapStateToProps = state => {
return {
data: state.data
};
};
export default withRouter(connect(mapStateToProps)(Test));
How do I test this Test (functional) component using Enzyme? Am not sure how this mock works since the render method is inside the container which in turn invokes the renderChildren method in our functional component. Lot of functional components extends this container so we can't change this to composition pattern as of now.
Here's the test case (Not working)
import jsdomWindow from '../../test.setup';
import React from 'react';
import {BrowserRouter} from 'react-router-dom';
import { Test } from 'Client/containers/test';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Enzyme, {mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import sinon from 'sinon';
import * as api from 'API/request';
import { populateData, isDataStale } from '../../../../src/client/app/container';
Enzyme.configure({ adapter: new Adapter() });
const sandbox = sinon.sandbox.create();
describe('<Test />',() => {
beforeEach(function() {
sandbox.stub(api, 'default').returns(Promise.resolve({data: { saySomething: 'Yay Tests!'}}));
});
afterEach(function() {
sandbox.restore();
});
const match = {
params: {
id: 100
}
};
const testState = {
data : {
test: {
id:'100',
email:'someone#something.com',
first_name:'John',
home_phone:'325-555-6564',
last_name:'Doe'
}
}
};
const wrapper = mount(
<BrowserRouter>
<Provider store={createStore(state => state, { data: testState.data })}>
<Test data={testState.data} dispatch={ sinon.spy() }
match={match} />
</Provider>
</BrowserRouter>
);
jest.mock('../../../../src/client/app/container', () => ({
populateData: jest.fn(),
isDataStale: jest.fn(),
getError: jest.fn(),
getParams: jest.fn(),
}));
it('should render', () => {
console.log(wrapper.debug());
populateData.mockReturnValueOnce({});
isDataStale.mockReturnValueOnce(false);
const greetingContainer = wrapper.find('.greeting-container').find('.quick-info').find('.quick-info-heading');
expect(greetingContainer.text()).toContain('PROPER RESPONSE');
});
// it('should render greeting component by default', () => {
// expect(wrapper.find('div.greeting-container')).toBeDefined();
// expect(wrapper.find('div.info-container')).toBeDefined();
// expect(wrapper.find('div.next-button')).toBeDefined();
// });
// it('should not render greeting and next-button components on clicking next', () => {
// console.log(`Test**** ${JSON.stringify(wrapper.find('div.plan'))}`);
// });
});
Could anyone help me to unit test this using mount method of enzyme. Don't want to use shallow here as i have to test nested components' properties.
Thank you.
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={[]}/>);
Here is a simplified version of my component:
import React from 'react';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
import { fromPromise } from 'mobx-utils';
#observer
export class MyComponent extends React.Component {
#action componentDidMount() {
const { store, params } = this.props;
this.warehouse = store.findById(params.id);
}
#observable warehouse = fromPromise(Promise.resolve());
render() {
return this.warehouse.case({
fulfilled: (value) => (
<div>
fulfilled
</div>
),
rejected: (error) => (
<div>
rejected
</div>
),
pending: () => (
<div>
pending
</div>
)
});
}
}
And here is my test (using jest and enzyme):
import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { observable, when } from 'mobx';
import { fromPromise } from 'mobx-utils';
import { MyComponent } from './MyComponent';
describe('<MyComponent>', () => {
it('should render correctly for state "fulfilled"', (done) => {
const mockStore = observable({
findById: jest.fn(() => fromPromise(Promise.resolve({ id: 'id' })))
});
const wrapper = mount(<MyComponent store={mockStore} params={{ id: '1' }} />);
const wh = wrapper.instance().warehouse;
when(
() => wh.state === 'fulfilled',
() => {
expect(wrapper.text()).toBe('fulfilled');
done();
}
);
});
});
The problem is that the handler for when in test runs before render method, so I don't have access to rendered markup there.
My question is how to run my except codes after rendering the fulfilled state.
Also I don't want to hack my component. Here I am using wrapper.instance().warehouse which I don't like very much.
Generally, the question would be how to test components with observable states in them?
I ended up with this solution:
import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { observable, when } from 'mobx';
import { fromPromise } from 'mobx-utils';
import { MyComponent } from './MyComponent';
describe('<MyComponent>', () => {
it('should render correctly for state "fulfilled"', (done) => {
const mockStore = observable({
findById: jest.fn(() => fromPromise(Promise.resolve({ id: 'id' })))
});
const wrapper = mount(<MyComponent store={mockStore} params={{ id: '1' }} />);
const wh = wrapper.instance().warehouse;
when(
() => wh.state === 'fulfilled',
() => {
process.nextTick(() => {
expect(wrapper.text()).toBe('fulfilled');
done();
});
}
);
});
});
Also there is a related question in mobx-react project issues.