Jose the hose
Jose the hose

Reputation: 1895

Unit test with moment.js

I'm new to writing unit tests.

My function looks like this:

getData() {
return this.parameters.map(p => {
        return {
            name: p.name,
            items: p.items.map(item => {

                const toTime = item.hasOwnProperty('end') ? moment.utc(item.end._d).unix() : null;
                const fromTime = item.hasOwnProperty('start') ? moment.utc(item.start._d).unix() : null;

                return {
                    id: item.id,
                    fromTime: fromTime,
                    toTime: toTime,
                };
            }),
        };
    });
}

and so far my Jasmine test looks like this:

describe('getData()', function() {
it('should return json data', function() {
    $ctrl.parameters = [{
        name: 'test',
        items: [{
            id: 1,
            fromTime: null,
            toTime: null
        }, {
            id: 13,
            fromTime: null,
            toTime: null

        }]
    }];

    expect($ctrl.getData()).toEqual([{
        name: 'test',
        items: [{
            id: 1,
            fromTime: null,
            toTime: null
        }, {
            id: 13,
            fromTime: null,
            toTime: null
        }]
    }]);
});
});

This test is working/passing, but as you can see I am not testing the ternary if/else that uses Moment.js. Basically what the ternary does is check if items contains a property called start / end and if it does, convert that value to a epoch/Unix timestamp and assign it to either toTime or fromTime.

So if items had a property called end with a value of 'Sat Oct 31 2015 00:00:00 GMT+0000 (GMT)', then it would be converted to '1446249600' and assigned to toTime.

How can I write a test for it?

Upvotes: 14

Views: 6388

Answers (1)

Lukas S.
Lukas S.

Reputation: 5758

The simplest option is to just construct a couple example dates manually for the input. For example:

$ctrl.parameters = [{
    name: 'test',
    items: [{
        id: 1,
        start: moment.utc('2017-01-01T01:00:00'),
        end: moment.utc('2017-01-01T06:00:00')
    }, {
        id: 13,
        start: moment.utc('2017-01-02T08:00:00'),
        end: null

    }]
}];

(Note in the above example, I changed fromTime and toTime to start and end, respectively, since that is what getData is expecting for the input.)

Then figure out their unix timestamps. You can do this part externally - for example, I just opened up the browser developer tools (F12) on the moment.js website, evaluated the following statements in the console, and grabbed the timestamp values:

moment.utc('2017-01-01T01:00:00').unix()
moment.utc('2017-01-01T06:00:00').unix()
moment.utc('2017-01-02T08:00:00').unix()

Finally, back in the unit test, just verify that the timestamps match the expected values:

expect($ctrl.getData()).toEqual([{
  name: 'test',
  items: [{
    id: 1,
    fromTime: 1483232400,
    toTime: 1483250400
  }, {
    id: 13,
    fromTime: 1483344000,
    toTime: null
  }]
}]);

Alternatively, if you would rather not have hardcoded timestamps in your unit tests, you can instead store each example date in its own variable (e.g., start1, end1), and then compare to, e.g., start1.unix():

// Arrange
const start1 = moment.utc('2017-01-01T01:00:00');
const end1 = moment.utc('2017-01-01T06:00:00');
const start2 = moment.utc('2017-01-02T08:00:00');
$ctrl.parameters = [{
    name: 'test',
    items: [{
        id: 1,
        start: start1,
        end: end1
    }, {
        id: 13,
        start: start2,
        end: null

    }]
}];

// Act
const result = $ctrl.getData();

// Assert
expect(result).toEqual([{
  name: 'test',
  items: [{
    id: 1,
    fromTime: start1.unix(),
    toTime: end1.unix()
  }, {
    id: 13,
    fromTime: start2.unix(),
    toTime: null
  }]
}]);

That's perfectly fine, since the unit test is meant to test your code, not moment.js. It's up to you.

Note also that I am using the Arrange-Act-Assert pattern for organizing the test. Again, up to you, but once your unit tests start to get complicated, that tends to keep things easier to follow.

Either way, you will need to change how you compute toTime and fromTime in your getData method, since the code as written will not work if you pass in null for either start or end. In particular, item.hasOwnProperty('start') will return true if you pass in a null value for start, but it will error out because it tries to evaluate item.start._d. Instead, I recommend changing those 2 lines to the following:

const toTime = item.end ? moment.utc(item.end._d).unix() : null;
const fromTime = item.start ? moment.utc(item.start._d).unix() : null;

I would also advise against using the _d property of the moment object, since that is an internal (private) variable. Since start and end are already moment objects, you may instead be able to just do this:

const toTime = item.end ? item.end.unix() : null;
const fromTime = item.start ? item.start.unix() : null;

A complete jsbin example containing all of the above recommended changes is available here:

https://jsbin.com/xuruwuzive/edit?js,output

Note that some tweaks had to be made so it runs outside the context of AngularJS (make getData a standalone function that accepts its parameters directly, rather than via $ctrl).

Upvotes: 7

Related Questions