How to get value of react app state['Key'] in test? - reactjs

I'm trying to make an test for a react application, but I have run into a problem. I couldn't get jest to work properly so I have been trying to work around it which has caused my current problem. The reason my test isn't working is because the way I'm calling for the values of the keys in state requires me to be using enzyme in jest. Is their a way to get the values of the keys in state inside the react app without using jest and how do I do it?
this is the function in my react app:
setTimeMonth = (time) => {
const today = moment().format('YYYY-MM-DD');
const before = moment().subtract(time, 'months').format('YYYY-MM-DD');
this.setState({Date2: today});
this.setState({Date1: before});
}
this is the test for the function:
it('setTimeMonth(number)', () => {
const wrapper = new ReactPage;
expect(wrapper.state('Date1').toMatch(""));
expect(wrapper.state('Date2').toMatch(""));
wrapper.setTimeMonth(1);
expect(wrapper.state('Date1').toMatch(moment().format('YYYY-MM-DD')));
expect(wrapper.state('Date2').toMatch(moment().subtract(1, 'Month').format('YYYY-MM-DD')));
});

Here is a working example:
example.js:
import * as React from 'react';
import moment from 'moment';
export class Example extends React.Component{
constructor(props) {
super(props);
this.state = { Date1: '', Date2: '' };
}
setTimeMonth = (time) => {
const today = moment().format('YYYY-MM-DD');
const before = moment().subtract(time, 'months').format('YYYY-MM-DD');
this.setState({Date2: today});
this.setState({Date1: before});
};
render() {
return <div/>
}
}
example.test.js:
import * as React from 'react';
import { shallow } from 'enzyme';
import moment from 'moment';
import { Example } from './example';
describe('Example', () => {
it('setTimeMonth(number)', () => {
const wrapper = shallow(<Example/>);
expect(wrapper.state('Date1')).toBe('');
expect(wrapper.state('Date2')).toBe('');
wrapper.instance().setTimeMonth(1);
expect(wrapper.state('Date2')).toMatch(moment().format('YYYY-MM-DD'));
expect(wrapper.state('Date1')).toMatch(moment().subtract(1, 'months').format('YYYY-MM-DD'));
});
});

Related

how to convert class component to function component?

I am new to React and I am trying to convert this code below to a function component, but it doesn't work, I have never used class components. Could anyone help me to convert it?
Thanks in advance.
import React from 'react'
import ReactDOM from 'react-dom'
import ScrollSnap from 'scroll-snap'
import './styles.css'
class App extends React.Component {
container = React.createRef()
bindScrollSnap() {
const element = this.container.current
new ScrollSnap(element, {
snapDestinationY: '90%',
}).bind()
}
componentDidMount() {
this.bindScrollSnap()
}
render() {
return (
<div ref={this.container}>
</div>
)
}
Here's what you need to do:
Replace createRef with useRef which is the functional hook to be used in functional components.
Replace componentDidMount with useEffect() with an empty array as dependency array, which basically runs that code once, on mount.
const App = () => {
const containerRef = React.useRef(null);
const bindScrollSnap = () => {
new ScrollSnap(containerRef , {
snapDestinationY: '90%',
}).bind()
}
React.useEffect(() => {
bindScrollSnap();
}, []);
return <div ref={containerRef}></div>
}

Mocking moment.js react props with jest and enzyme

Say i have a child component that takes two moment objects as props such as:
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
class ChildComponent extends React.Component {
constructor(props) {
super(props);
}
startAndEndDateOnSameDay() {
return this.props.startDate.isSame(this.props.endDate, 'date')
}
render() {
let formattedDate;
if(this.startAndEndDateOnSameDay()) {
formattedDate = this.props.startDate.format();
}
else {
formattedDate = this.props.endDate.fromNow();
}
return (
<div>{formattedDate}</div>
);
}
}
ChildComponent.propTypes = {
startDate: PropTypes.instanceOf(moment).isRequired,
endDate: PropTypes.instanceOf(moment).isRequired
}
export default ChildComponent;
And a parent component that passes down two moment objects to the child component like:
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import ChildComponent from './ChildComponent';
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
startDate: moment(),
endDate: moment()
};
}
render() {
return (
<ChildComponent startDate={this.state.startDate} endDate={this.state.endDate}/>
);
}
}
export default ParentComponent;
And I am trying to test these components with jest and enzyme with:
import React from 'react';
import { shallow } from 'enzyme';
import ParentComponent from '../components/ParentComponent';
describe('<ParentComponent />', () => {
let wrapper;
beforeAll(() => {
wrapper = shallow(<ParentComponent/>);
});
it('should render the component correctly', () => {
expect(wrapper).toMatchSnapshot();
});
});
And
import React from 'react';
import { shallow } from 'enzyme';
import ChildComponent from '../components/ChildComponent';
import moment from 'moment';
describe('<ChildComponent />', () => {
let wrapper;
beforeAll(() => {
wrapper = shallow(<ChildComponent startDate={moment()} endDate={moment()}/>);
});
it('should render the component correctly', () => {
expect(wrapper).toMatchSnapshot();
});
describe('when the the start date and end date are on the same day', () => {
it('should print a the formatted start date', () => {
expect(wrapper.text()).toEqual('mock format here');
});
});
describe('when the start and end date are not on the same day', () => {
it('should print the the end date from now', () => {
expect(wrapper.text()).toEqual('mock from now here');
});
});
});
How do I mock the moment.js library functions in order for my tests to work?
I am trying to create a manual mock file within the __mocks__ folder that will mock the moment library's functions for both test suites. The current issues I am running into:
How do i mock the moment() constructor from within the mock file to always return the same date, so my snapshot tests will always pass?
How do i mock the the .isSame(), .format(), and .fromNow() functions to always return the same value regardless of the current time?
So far, I have the follow test file that breaks all my tests. I was following the documentation here:
const moment = require('moment');
function isSame() {
return true;
}
function fromNow() {
return 'Tomorrow at 12:00 pm'
}
function format() {
return 'Sept 16th 19';
}
exports.isSame = isSame;
exports.fromNow = fromNow;
exports.format = format;
When I use this file, i get errors within my component that say startDate and endDate are undefined.
I ended up solving this by creating a manual mock in the mocks folder. The key is that moment exports it's prototype with moment.fn.
import fixtures from '../__fixtures__/moment';
const moment = require.requireActual('moment');
// By default, the isSame function will always return true
let isSame = true;
moment.fn.isSame = () => isSame;
moment.fn.calendar = () => fixtures.CALENDAR;
moment.fn.fromNow = () => fixtures.FROM_NOW;
// Since format is often called with a variety of format strings, we need to
// differentiate how we are calling the function
moment.fn.format = (format) => {
switch (format) {
case 'MMM Do YY':
return fixtures.DATE;
case 'H:mm a':
return fixtures.TIME;
case 'MMM Do YY H:mm a':
return fixtures.DATETIME;
default:
return Error('Unsupported format in moment mock. Add case to __mocks__/moment.js');
}
};
moment.duration.fn.humanize = () => fixtures.DURATION;
// This function is added to moment's prototype in order for our tests to
// change moment's isSame behaviour
moment.fn.__isSame = (value) => { isSame = value; };
// This resets the isSame behaviour back to default
moment.fn.__reset = () => {
moment.fn.isSame = () => true;
};
export default moment;
Jest automatically loads this file when running the tests, and I can then test my components like:
it('renders a moment duration', () => {
expect(wrapper.text()).toEqual(fixtures.DURATION);
});
If i need to change the behavior of the .isSame function, I can modify it with:
import moment from 'moment';
beforeAll(() => {
moment.fn.__isSame(false);
});
If you are using JEST to test, I can recommend you https://github.com/hustcc/jest-date-mock Then you can mock current date using
advanceTo(new Date(2018, 5, 27, 0, 0, 0));
Best practice to test is to mock fixed date and compare with result

Context API Unit test failing with TypeError

I have a HOC component WithContext (in a file conveniently named withContext.js) as follows
import React from 'react';
import { DEFAULT_STATE } from './store';
const MyContext = React.createContext(DEFAULT_STATE);
export function WithContext(Component) {
return function WrapperComponent(props) {
return (
<MyContext.Consumer>
{state => <Component {...props} context={state} />}
</MyContext.Consumer>
);
};
};
and a component that uses it as follows
import React from "react";
import { WithContext } from './withContext';
const MyComp = (context) => {
return (
<div className="flex dir-col" id="MyComp">
<p>This is a test</p>
</div>
)
};
export default WithContext(MyComp);
I also have a unit test that aims to test this MyComp component. The unit test follows
import React from "react";
import {shallow} from "enzyme";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { WithContext } from './withContext';
// We need to configure our DOM
import jsdom from 'jsdom'
const {JSDOM} = jsdom;
const {document} = (new JSDOM('<!doctype html><html><body></body></html>')).window;
global.document = document;
global.window = document.defaultView
Enzyme.configure({
adapter : new Adapter()
})
beforeEach(() => {
jest.resetModules()
})
//Takes the context data we want to test, or uses defaults
const getMyContext = (context = {
state : {}
}) => {
// Will then mock the MyContext module being used in our MyComp component
jest.doMock('withContext', () => {
return {
MyContext: {
Consumer: (props) => props.children(context)
}
}
});
// We will need to re-require after calling jest.doMock.
// return the updated MyComp module that now includes the mocked context
return require('./MyComp').default;
};
describe("MyComp component loading check", () => {
test("Renders the MyComp component correctly", () => {
const MyCompContext = getMyContext();
const wrapper = shallow(<MyComp/>);
// make sure that we are able to find the header component
expect(wrapper.find(".flex").hostNodes().length).toBe(1);
});
});
However this test keeps failing with the message
TypeError: (0 , _withContext.WithContext) is not a function
};
export default WithContext(MyComp);
Can you please let me know what is wrong here ?
Thanks
Looks like you are mocking withContext with jest.doMock but the mock you are returning from the factory function does not contain a WithContext function.
Then when you require('./MyComp').default it is using the withContext mock within your MyComp module and failing when it tries to export default WithContext(MyComp); since the withContext mock does not define a WithContext function.

enzyme shallow rendering just one node in redux components

Enzyme shallow rendering behaves in an unexpected way if I am rendering a redux component with a mocked store.
I have a simple test that looks like this :
import React from 'react';
import { shallow } from 'enzyme';
import { createMockStore } from 'redux-test-utils';
import Test from './Test'
it('should render ', () => {
const testState = {
app: {
bar: ['a', 'b', 'c']
}
};
const store = createMockStore(testState)
const context = {
store,
};
const shallowComponent = shallow(<Test items={[]}/>, {context});
console.log(shallowComponent.debug());
}
The Test component looks like :
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
export default Test;
Which as expected prints out this :
<div className="here" />
However if my component is a redux component :
class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
const mapStateToProps = state => {
return {
barData: state.app.bar
}
}
export default connect(
mapStateToProps
)(Test)
Then what I get in the console is this :
<BarSeriesListTest items={{...}} barData={{...}} dispatch={[Function]} />
Why is there this difference? How do I test that my component has <div className="here"/> embedded in it in my redux version of the component?
You are referencing the HOC that connect is returning and not the component that you want to test.
You should use enzyme's dive function which will render the child component and return it as a wrapper.
const shallowComponent = shallow(<Test items={[]}/>, {context}).dive();
You can use it multiple times if you have multiple components that you need to dive through to get to. It's better than using mount as well because we are still testing in isolation.
You should export the unconnected component and test it separately (notice the first export):
export class Test extends React.Component {
}
...
export default connect(
mapStateToProps
)(Test)
While in your test you should test the rendering of the unconnected component like so (notice the curly braces around { Test }):
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Test } from './Test';
describe('...', () => {
it('...', () => {
const wrapper = shallow(<Test />)
expect(toJson(wrapper)).toMatchSnapshot();
})
})
Hope this helps.
Mode specifically for your described case the component should be:
import React from 'react';
import { connect } from 'react-redux';
export class Test extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className="here"/>
)
}
}
const mapStateToProps = state => {
return {
barData: state.app.bar
}
}
export default connect(
mapStateToProps
)(Test)
The test spec should be:
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Test } from 'Test';
describe('Test component', () => {
it('renders', () => {
const wrapper = shallow(<Test />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
Which generates the following snapshot:
exports[`Test component renders 1`] = `
<div
className="here"
/>
`;
You are exporting the connected component by default. What you can do is import the component that is not connected to redux.
import { Test } from './Test';
Then your test should work.

React-Dimension getWrappedInstance() returns undefined in enzyme test

I ran into an issue on create test for a reactJs component that is wrapped by React-Dimension as an HOC. Here is the simple set up
import React, { PropTypes } from 'react';
import Dimensions from 'react-dimensions';
export class CustomComponent extends React.Component {
static propTypes = {
messageProp: PropTypes.string.isRequired
};
constructor(props, context) {
super(props, context);
this.state = {
message: props.messageProp
};
};
// a simple function to be tested
sayHi () {
return this.state.message;
}
render () {
return (<div>{this.sayHi()}</div>);
}
}
export default Dimensions({elementResize: true})(CustomComponent);
And here is the test
import React from 'react';
import { mount, shallow } from 'enzyme';
import CustomComponent from 'components/CustomComponent/CustomComponent.js';
describe('<CustomComponent /> presentation', () => {
let _props = {
messageProp: 'Hi Message !!'
}
it('HOC Wrapped Custom test ', () => {
const c = mount(<CustomComponent {..._props}/>);
const inst = c.instance();
const childInst = inst.getWrappedInstance();
expect(childInst.sayHi()).to.equal('Hi Message !!');
});
});
The getWrappedInstance() is returning undefined. If I don't put the Dimension as the wrapping HOC, the test case will work just fine. Did I missed something so the return of the getWrappedInstance() is undefined?
I have tried to use mount or sallow and not changing the result. What I need to test is the sayHi function of the CustomComponent in this case.

Resources