Reading Time: 7 minutes

“It’s more about good enough than it is about right or wrong.”

James Bach

Disclaimer: Before I start talking about adding automatic tests into the project using Jest, I need to point out two things:

  1. I’m not a Javascript developer. I feel far more comfortable with PHP, Java, or almost any OOP language. So keep in mind that this is not a “You should do it like this” but more of an “I did it like this, how can I improve?”-kind of article.
  2. I chose the quote above for a reason. There are certain methods in this code that are either unnecessary to be tested or far easier to test them with a (manual) functional test. So the aim is not to achieve 100% code coverage or a ‘perfect’ codebase. The goal is to add testing to feel more confident about my code and detect possible bugs.

With that being said, let’s grab a coffee and talk about: Testing.

Why automated testing?

We already saw in the first two parts of the series that our code is working and successfully adding time bookings by mapping Google Calendar events with a .csv file. Thus the question comes to mind: why should we even bother writing automated tests?

There are several good reasons why you should write unit tests, and a lot of brilliant people have written about it! I will link some excellent articles at the end of this post.

But here are some of my key points:

  1. Automatic tests save time because you don’t need to manually check your whole workflow/application when you’ve changed just a small piece of code.
  2. Automatic tests point you towards architectural flaws in your code.
  3. Automatic tests give you confidence that the part that you tested is working.
  4. Automatic tests can ensure that you didn’t break functionality when changing your code.
  5. Automatic tests have a documentation function. When you have a specific bug or edge case, you can write a test case for this particular situation to let others know about this specific behavior.

Additionally to those key points, I have the feeling that a lot of beginning programmers don’t know about the fantastic benefits of automatic testing or they want to learn it but don’t know how to start.

How to start?

In Javascript, writing tests is relatively easy when you decided on which testing framework you want to pick. There is Mocha, Karma, Jasmine, Pupeteer, Jest, AVA, Tape, QUnit, Chai, and even more!

While I’ve been working with Mocha and Jasmine before, I decided to pick Jest this time. Jest is developed by Facebook and has gained a lot of reach by its combination with ReactJS. For this reason, I am fairly confident that this testing framework will be maintained and used for quite some time and won’t be abandoned during the next one or two years.

npm install --save-dev jest

While Jest is merely looking for files ending with .test.js, I feel it gives a better overview to move all test files into /tests and the business logic which is currently located at / into /src.

What should I test?

Or rather, what should I not test?

There are functions that don’t need testing. Usually, those are getter and setters (as they don’t hold any logic), as well as some creational methods (in our code base, for example, getTimeMax(), which is currently only creating a new Date object). Now I know, this is already worth a discussion. You could argue that getTimeMax() might be changed in the future, and the change could break other functionality. If you choose this or that path really depends on the project, your personal preference, your confidence in your codebase, or the rules your team agreed upon.

For this small side project in the current situation, I’ve decided not to test several methods. For example, I will not write unit tests for Google authentication or how to get the mappings from a .csv-file into an array of objects. Essentially, I’m not going to test methods that are only calling a third-party API as well as I am not testing the third-party library itself.

Testable code

If you haven’t checked out part 1 and part 2 of the series, now might be the perfect time. For those who have I will quickly recapitulate:

  • In part 1, I scribbled down an idea in Javascript so we can get Google calendar events, map them with a set of predefined project- and service-ids and create time tracking entries for Mite.
  • In part 2, I cleaned up the code so that we now have separate classes with different responsibilities instead of 2 files with quick a lot of code without a proper structure.

Because we did all this preparatory work in part 2, we can now start testing (and mocking) classes. From my experience, this is where a lot of ambitious programmers fail because they want to write tests for code that has grown over the years, has far too many dependencies (and by that usually too many responsibilities, also) and often is not in a testable state. It doesn’t mean that you cannot test it at all, but it makes testing extremely hard and very difficult for beginners. For this reason, I like to add unit tests to a project as soon as possible because (see Key Point 2):

Automatic tests point you towards architectural flaws in your code.

Dependency Injection

Coming from a PHP/Java background, I am very used to injecting all dependencies into the classes by some kind of DI container. The reason why this is more commonly used in languages like C++, Java, or PHP than in JS projects is that it is absolutely needed there. In Javascript, you can survive without using Dependency Injection, and that’s why I won’t use it right now. But if you want to read more about it, I can recommend these two articles for you:

https://tsh.io/blog/dependency-injection-in-node-js/

https://levelup.gitconnected.com/node-js-advanced-pattern-dependency-injection-container-fc58a1946638

Let’s start testing!

After deciding which of the service won’t be tested, there are only three classes left that need testing: Mite.js, DateTimeHelper.js, EventMapper.js. Starting with the tests for getProjectAndServiceMapping from EventMapper, there are two scenarios I want to automatically test:

  1. Successfully finding a mapping for a calendar entry returns [projectId, serviceId].
  2. Not finding a mapping returns [null, null].
const EventMapper = require('../src/EventMapper.js');

describe('getProjectAndServiceMapping', () => {
    test('should get correct project and service mapping', () => {
        const mockMappings = [
            {keyword: '#321', projectId: 0, serviceId: 0},
            {keyword: '#123', projectId: 1234, serviceId: 4321},
            {keyword: '#456', projectId: 9876, serviceId: 5432}
        ];

        const [projectId, serviceId] = EventMapper.getProjectAndServiceMapping(mockMappings, 'summary #123');

        expect(projectId).toBe(1234);
        expect(serviceId).toBe(4321);
    });

    test('should return array with two null values', () => {
        const mockMappings = [];
        const [projectId, serviceId] = EventMapper.getProjectAndServiceMapping(mockMappings, 'summary #123');

        expect(projectId).toBe(null);
        expect(serviceId).toBe(null);
    })
});

You’ll notice for the first test that I added two additional mappings. This is used to make sure that the method is not somehow taking the first or last mapping for every entry but precisely the one keyword that is included in the calendar entry title. As mentioned before, I decided against testing getMappings right now because it only reads a .csv file into an array of objects.

DateTimeHelper.js

For DateTimeHelper.js there are two methods we should test, and thus I added a describe block for both of them. Through this, we get a nicely readable description of our tests when running our tests (for example) with PhpStorm:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c9057bca-1891-4a08-89b6-eee09c3a02e2/Screenshot_2020-06-14_at_22.42.20.png
const DateTimeHelper = require('../src/DateTimeHelper.js');

describe('formatDate', () => {
    test('should return date as YYYY-MM-DD', () => {
        let input = '2020-01-01T00:00:00-02:00';
        let result = DateTimeHelper.formatDate(input);

        expect(result).toBe('2020-01-01');
    });
});

describe('getDurationInMinutes', () => {
    test('should return 60 minutes', () => {
        let start = new Date();
        let end = new Date(start.getTime() + (60 * 60000));

        let result = DateTimeHelper.getDurationInMinutes(start, end);
        expect(result).toBe(60);
    });
});

Additionally, we could test what happens if the input parameters are not Date objects as expected, but the actual input comes directly from Google Calendar entries, which should always return a start and end date.

Mite

Two tests are done, one more to go, and we haven’t even used Mocks yet! But for Mite.js we need the mock capabilities of Jest now. The reason is that Mite.js is using the Config.js file as well as the EventMapper.js to create the correct entry format to save the entries in Mite. Instead of Dependency Injection, we are currently only using require to load dependencies. So we cannot inject a fake implementation into the class, and therefore we would always get different results depending on your personal config values and mappings. We would have a hard time reproducing failing tests like this, so instead, we use jest.mock to replace the actual implementation with a fake one.

jest.mock('../src/Config', () => ({
    MITE_API_KEY: 'apiKey',
    MITE_ACCOUNT: 'miteAccount',
    INPUT_PATH: 'inputPath',
    GOOGLE_AUTH_DIRECTORY: 'authDirectory'
}));

jest.mock('../src/EventMapper', () => ({
    getProjectAndServiceMapping: () => [123, 456]
}));

Now we know exactly which values will be used to generate the Mite entry format. All we have to do now is faking a Google Calendar entry (event) and initialize it with the necessary parameters.

let start = new Date('2020-01-01T00:00:00-02:00');
let end = new Date(start.getTime() + (60 * 60000));

const event = {
    start: {dateTime: start},
    end: {dateTime: end},
    summary: 'summary'
};

All that is left to do is testing if the actual result is the same as we expect it. We could either check every parameter separately or use deep matching with Jest’s toStrictEqual assertion. Here is the complete test file:

const Mite = require('../src/Mite');

jest.mock('../src/Config', () => ({
    MITE_API_KEY: 'apiKey',
    MITE_ACCOUNT: 'miteAccount',
    INPUT_PATH: 'inputPath',
    GOOGLE_AUTH_DIRECTORY: 'authDirectory'
}));

jest.mock('../src/EventMapper', () => ({
    getProjectAndServiceMapping: () => [123, 456]
}));

test('generateEntryFormat return values', () => {
    let start = new Date('2020-01-01T00:00:00-02:00');
    let end = new Date(start.getTime() + (60 * 60000));

    const event = {
        start: {dateTime: start},
        end: {dateTime: end},
        summary: 'summary'
    };

    let entry = Mite.generateEntryFormat(event, []);

    const expectedResult = {
        date_at: '2020-01-01',
        minutes: 60,
        note: 'summary',
        project_id: 123,
        service_id: 456
    }
    expect(entry).toStrictEqual(expectedResult);
})

Conclusion

As I mentioned in the beginning, this is probably not the best solution, but this is where you come into play! Please comment, discuss and contribute to the repository so we can learn from each other and learn about different solutions and best practices:

https://github.com/moritzwachter/calendar-to-mite/tree/0.4

And as I promised…

Here are some link and book recommendations for you:

https://refactoringjs.com/

https://dzone.com/articles/top-8-benefits-of-unit-testing

https://www.codemag.com/Article/1901071/10-Reasons-Why-Unit-Testing-Matters

https://thenewstack.io/unit-testing-time-consuming-product-saving/

Moritz Wachter

Author Moritz Wachter

More posts by Moritz Wachter