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.
Related
I have a local function that should be called on a button click and set the state of a Boolean variable inside it. I tried to add unit test to this module to identify whether the button is clicked and the function is called following the button click.
But my test is failing. I tried by mock the function inside the 'describe' method yet it didn't work.
SomeComponent.js
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
openImagesDialog: false,
}
}
fuctionToBeCalled = (val) => {
this.setState({ openImagesDialog: val })
}
render() {
return (
<a onClick={() => this.fuctionToBeCalled(true)} className="img-container float">
{child components....}
</a>
)
}
}
SomeComponent.test.js
import React from 'react';
import Enzyme, { shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import SomeComponent from '../SomeComponent';
import SomeAnotherComp from "../SomeAnotherComp";
Enzyme.configure({ adapter: new Adapter() })
function setup() {
const props = {
openImagesDialog: false
}
let enzymeWrapper = shallow(<SomeComponent {...props} />)
return {
props,
enzymeWrapper
}
}
describe('components', () => {
const { props, enzymeWrapper } = setup()
it('should call fuctionToBeCalled(){}', () => {
const SomeAnotherCompProps = enzymeWrapper.find(SomeAnotherComp).props()
const fuctionToBeCalled = jest.fn(()=>true);
enzymeWrapper.find('a').simulate('click')
expect(fuctionToBeCalled).toBeCalled();
//expect(SomeAnotherCompProps.dialogOpen).toBe(true)
})
})
I'd like to know is there any other way to try this out.
Firstly, openImagesDialog is not a prop but a state in the component.
Secondly, fuctionToBeCalled is a function defined on component instance and you need spy on it instead of just creating a mock function . In order to do that you use spyOn on component instance. You can also check the state after simulating click
import React from 'react'
import Enzyme, { shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import SomeComponent from '../SomeComponent'
import SomeAnotherComp from "../SomeAnotherComp";
Enzyme.configure({ adapter: new Adapter() })
function setup() {
const props = {
openImagesDialog: false
}
let enzymeWrapper = shallow(<SomeComponent {...props} />)
return {
props,
enzymeWrapper,
}
}
describe('components', () => {
const { props, enzymeWrapper } = setup()
it('should call fuctionToBeCalled(){}', () => {
const SomeAnotherCompProps = enzymeWrapper.find(SomeAnotherComp).props()
const instance = enzymeWrapper.instance();
jest.spyOn(instance, 'fuctionToBeCalled');
enzymeWrapper.find('a').simulate('click')
expect(instance.fuctionToBeCalled).toBeCalled();
expect(enzymeWrapper.state('openImagesDialog')).toEqual(true);
})
})
I have a react component that renders conditionally (renders if data is fetched otherwise returns null) and I want to test this with jest & enzyme. The problem that I'm having is I want to test one of the methods in the class but .instance() keeps returning null so it doesn't allow me to test the instance.
my code looks something like this
export default class MyComponent extends React.Component<Props, State> {
componentDidMount() {
this.props.fetchData.then(() =>
this.setState({ loaded: true });
);
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.loaded) {
// render stuff here
} else {
return null;
}
}
}
and in the test I want to test
describe('myComponent', () => {
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>);
const method = shallowWrapper.instance().methodThatIWantToTest();
....such and such
});
});
but it looks like MyComponent only returns null so shallowWrapper.instance() returns null as well. I tried shallowWrapper.update() and many other things but it seems it doesn't want to render at all.. How do I wait for my component to be updated and then starts expect statement?
has anyone had a similar issue as mine and know how to work around this?
It is render result and not an instance that is null. shallowWrapper.instance() is an instance of component class, it cannot be null for stateful component. As the reference states:
Returns (React 16.x)
ReactComponent: The stateful React component instance.
null: If stateless React component was wrapped.
While shallowWrapper.html() will be initially null indeed.
There is a mistake in original code, it should be this.state.loaded and not this.loaded:
MyComponent extends React.Component {
state = { loaded: false };
componentDidMount() {
this.props.fetchData.then(() => {
this.setState({ loaded: true });
});
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.state.loaded) {
return <p>hi</p>;
} else {
return null;
}
}
}
componentDidMount and methodThatIWantToTest should be preferably considered different units. They belong to different tests. In case methodThatIWantToTest is called in lifecycle hooks, it may be stubbed in componentDidMount test:
it('should fetch data', async () => {
const props = { fetchData: Promise.resolve('data') };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
await props.fetchData;
expect(shallowWrapper.html()).toBe('<p>hi</p>');
});
Then the method can be tested separately. Lifecycle hooks can be disabled to reduce number of moving parts:
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>, {disableLifecycleMethods: true});
const result = shallowWrapper.instance().methodThatIWantToTest();
expect(result).toBe(...);
});
Here is a working example:
myComponent.js
import * as React from 'react';
export default class MyComponent extends React.Component {
constructor(...props) {
super(...props);
this.state = { loaded: false };
}
componentDidMount() {
this.props.fetchData().then(() =>
this.setState({ loaded: true })
);
}
methodThatIWantToTest() {
return 'result';
}
render() {
if (this.state.loaded) {
return <div>loaded</div>;
} else {
return null;
}
}
}
myComponent.test.js
import * as React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './myComponent';
describe('myComponent', () => {
it('should do some stuff', async () => {
const fetchData = jest.fn(() => Promise.resolve());
const props = { fetchData };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
expect(shallowWrapper.instance().methodThatIWantToTest()).toBe('result');
// pause the test and let the event loop cycle so the callback
// queued by then() within componentDidMount can run
await Promise.resolve();
expect(shallowWrapper.html()).toBe('<div>loaded</div>');
});
});
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 function in module called handlelistOfTemplates which calls actions defined in another file. I want to test when handlelistOfTemplates is called the function in actions file is called with correct parameters.
My container component :
import React from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import * as getData from '../../services/DataService';
class Container extends React.Component {
constructor(props){
super(props)
this.props.actions.getTemplates(1);
this.state = {
value: 1
}
}
handlelistOfTemplates = (template, index, value) => {
this.props.selectedTemplate(template);
this.setState({ value });
this.props.actions.getTemplates(template);
};
componentDidMount() {
}
render() {
return(
<ListOfTemplates listOfTemplates={this.props.listOfTemplates} value={this.state.value} onChange={this.handlelistOfTemplates}/>
);
}
}
function mapStateToProps({state}) {
return {
listOfTemplates: state.listOfTemplates
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(getData, dispatch)
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(Container);
And my test :
import React from 'react';
import sinon from 'sinon';
import expect from 'expect';
import { shallow } from 'enzyme';
import PropTypes from 'prop-types';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { createMockStore, createMockDispatch } from 'redux-test-utils';
import Container from './Container';
const shallowWithStore = (component, store) => {
const context = {
store,
muiTheme: getMuiTheme(),
};
const childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
return shallow(component, { context, childContextTypes });
};
let store;
const loadComponent = (testState) => {
const props = {
actions: {
getTemplates: () => {return Promise.resolve()}
}
}
store = createMockStore(testState)
return shallowWithStore(<Container {...props}/>, store);
}
const getFakeState = () => {
return {
listOfTemplates: [],
};
}
describe('Container', () => {
let testState, component;
describe("when Appeal template is selected from select template dropdown", () => {
beforeAll(() => {
testState = getFakeState();
component = loadComponent(testState);
});
fit('should update the content in editor', (done) => {
component.dive().find('ListOfTemplates').props().onChange('Appeal', 1, 2);
component.update();
done();
expect(component.dive().state().value).toEqual(2) // error value still is at 1
expect(component.instance().props.actions.getTemplates).toHaveBeenCalled();
});
});
});
When I run the above test I get the following error.
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function getTemplates]
Is there something else I should be running to get this to work ?may be my mock is not correct.
even i tried doing this :
jest.spyon(component.instance().props.actions, 'getTemplates'); before expect the error remains same.
Also, when i checking the component's local state has been modified or not. I'm not getting the updated state.
when i call component.dive().find('ListOfTemplates').props().onChange('Appeal', 1, 2);
the component.dive().state().value should become 2 but it is 1 instead.
Could you please help me where i'm doing wrong?
You should pass a Mock function getTemplate to your component, otherwise jest won't be able to check whether it was called or not.
You can do that like this: (notice jest.fn() )
const props = {
actions: {
getTemplates: jest.fn(() => Promise.resolve())
}
}
I have just started my first react job (yay!) and am attempting to write unit tests using jest. i am having an issue with mapDispatchToProps in my test.
please note, the test passes, but it is throwing the following error message:
Warning: Failed prop type: The prop testDispatch is marked as required in TestJest, but its value is undefined in TestJest
error seems to be test related to the test as it is not thrown when running the page in the web browser. also, mapStateToProps works fine, included to show it works.
using enzyme/shallow in order not to interact with the <Child/> component.
testJest.js
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import Child from './Child'
export class TestJest extends React.Component {
render() {
return (<div>ABC - <Child /></div>);
}
}
TestJest.propTypes = {
testDispatch: PropTypes.func.isRequired,
testState: PropTypes.arrayOf(PropTypes.string)
};
const mapStateToProps = (state) => {
return {
testState: state.utilization.pets // ['dog','cat']
};
};
const mapDispatchToProps = (dispatch) => {
return {
testDispatch: () => dispatch({'a' : 'abc'})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TestJest);
testJest-test.js
import React from 'react';
import { TestJest } from '../Components/testJest';
import TestJestSub2 from '../Components/TestJestSub2';
import { shallow } from 'enzyme';
describe('TESTJEST', () => {
it('matches snapshot', () => {
const wrapper = shallow(<TestJest />);
expect(wrapper).toMatchSnapshot();
});
});
You can pass empty function to TestJest as testDispatch prop:
it('matches snapshot', () => {
const wrapper = shallow(<TestJest testDispatch={() => {}} />);
expect(wrapper).toMatchSnapshot();
});
or pass jest mock function:
it('matches snapshot', () => {
const wrapper = shallow(<TestJest testDispatch={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
});
if you want to check if testDispatch was called (expect(wrapper.instance().props.testDispatch).toBeCalled();)