1
votes

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:

  1. 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?
  2. 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.

2

2 Answers

1
votes

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);
});
0
votes

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