简体   繁体   中英

Mocking es6 module returning factory function (moment.js)

Caveat: I'm new to Jest so bear.

I am attempting to test a Vue2.js filter using Jest called DateFilter. This filter simply applies a date format to a date passed to it.

DateFilter.js

import Vue from 'vue';
import moment from 'moment';

const dateFormatter = (dateValue, dateFormat) => {
    return moment(dateValue).format(dateFormat);
};

Vue.filter('date', dateFormatter);

export default dateFormatter;

So, I see three valid unit tests here

  1. The DateFilter module should export a function

  2. The date filter should initialize moment with the dateValue passed

  3. The date filter should call the format method on moment with the dateFormat passed

DateFilter.test.js

import moment from 'moment';
import DateFilter from './DateFilter';

describe('DateFilter', () => {
    it('should exist', () => {
        expect(DateFilter).toBeDefined();
        expect(typeof DateFilter).toBe('function');
    });

    it('should moment.format with the dateValue and dateFormat passed.', () => {
        // Here I get lost in how to spyOn moment function and the .format function
        const mockDateFormat = `dateFormat-${Math.random()}`;
        const mockDate = `mockDate-${Math.random()}`;
        jest.mock('moment', () => {
            return { format: jest.fn() }
        });
        // expect moment to have been called with mockDate
        // expect moment(mockDate) to have been called with mockDateFormat
    });
});

Not sure how much in details you want to test something, but I guess that the secret is a good mock of momentjs. Since you just want to test your dateFormatter you can do the next:

First we set the mock to momentjs:

jest.mock('moment', () => {
    const momentMock = { format: jest.fn() }

    return jest.fn(() => momentMock);
});

If instead of declaring const, you try to pass the object to jest.fn you will probably get errors that the function was not called, thats because we are going to generate different mocks everytime we call moment instead of having the same mock for all the moment calls.

This is a very simple one, you could do a more elaborated moment mock, but I think is not worth it the effort since your functions is simple enough.

Then, I think you have separated unit tests, you could have them in one but some times is better to assert chain of functions separately.

it('calls moment with the dateValue passed.', () => {
    const mockDateFormat = `dateFormat-${Math.random()}`;
    const mockDate = `mockDate-${Math.random()}`;

    dateFormatter(mockDate, mockDateFormat);

    expect(moment).toHaveBeenCalledWith(mockDate)
});
it('calls format with the dateFormat passed.', () => {
    const mockDateFormat = `dateFormat-${Math.random()}`;
    const mockDate = `mockDate-${Math.random()}`;

    dateFormatter(mockDate, mockDateFormat);

    expect(moment(mockDate).format).toHaveBeenCalledWith(mockDateFormat)
});

Disclaimer: In the second test it doesn't matter if you expect moment(), moment(mockDate) or moment('Whatever') , since you always mock the same thing you will receive the same format mock back.

In case you want something more complicated, you will need to create a fixture where you map the date that momentjs have been called, to a new object containing the mocked functions. But I believe this is more of a hassle and it is actually better for you to just test that moment has been called and that format has been called. Otherwise you are testing to much how the 3rd party library is working.

Hope this give you some guideline

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM