Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

Testing a Vue component part 4: more testing and faking time

Welcome to part 4 of a series on how to test a Vue component.

Here’s the table of contents of the series:

  1. Starting out with tests
  2. The first test and snapshot testing
  3. Creating an instance of a component
  4. More tests and faking time (you’re here)
  5. Jest awesomeness and a skeleton to get started
    More tests and faking time

More tests

If you navigate to http://vue-tabs-component.spatie.be/#second-tab you’ll notice that the tab named Second tab is automatically opened. Let’s take a look at the test that makes sure this functionality works.

it('uses the fragment of the url to determine which tab to open', async () => {
    window.location.hash = '#second-tab';

    await createVm();

    expect(document.body.innerHTML).toMatchSnapshot();
});

Let’s go through it in detail. First we set the hash of the current window.location (that’s JavaScript speak for the current url) to #second-tab. Remember, Jest comes with jsdom out of the box. It’s jsdom that provides us with that window instance.

Next we create a new Vue instance and await until Vue has done it’s thing. Finally we assert that the rendered output matches the saved snapshot. Of course I’ve manually verified, when that snapshot was first created, that it’s content was correct.

Asserting against a component property

Instead of using snapshots to make assertion you can also just assert against a property in the state of a Vue component. Let’s see how that works.

Our vue-tabs-component has a neat feature where you can make a tab use a custom fragment. Here’s the relevant test.

it('uses a custom fragment', async () => {
    document.body.innerHTML = `
        <div id="app">
            <tabs cache-lifetime="10">
                <tab id="my-fragment" name="First tab" >
                    First tab content
                </tab>
            </tabs>
        </div>
    `;

    const tabs = await createVm();

    expect(tabs.activeTabHash).toEqual('#my-fragment');
});

First we set up some html and we await until Vue has done it’s thing. Like mentioned before the createVm method return an instance of the Tabs component. We implemented our component in such a way that the activeTabHash-property in it’ state always contains the hash of the tab that’s being displayed. In that last line of the test we simply assert that the property contains the expected value.

Faking time

By default our tabs component will remember which was the last open tab. If you for instance navigate to http://vue-tabs-component.spatie.be/#second-tab and then to the same url without the fragment you’ll notice that the Second tab is being displayed.

The component remembers, for a limited amount of time, the last opened tab by writing the hash of the selected tab to local storage. Here’s the code that does that:

expiringStorage.set(this.storageKey, selectedTab.hash, this.cacheLifetime);

That expiringStorage class is something custom that is included in the project. It’s a wrapper around local storage. It makes items in local storage expiring.

expiringStorage has it’s own tests. Let’s take a look one of those tests.

it('remembers values by key', async () => {
    expiringStorage.set('my-key', 'my-value', 5);

    expect(expiringStorage.get('my-key')).toEqual('my-value');
});

Pretty simple right? We store my-value in the storage using the key my-key and then we assert that we get the same value when getting my-key. That last parameter is the lifetime in minutes.

Of course we should not only test that the value is being saved, but that is expires after five minutes.

Here’s the test for that:

it('returns null if the value has expired ', () => {
    expiringStorage.set('my-key', 'my-value', 5);

    progressTime(5);

    expect(expiringStorage.get('my-key')).toEqual('my-value');

    progressTime(1);

    expect(expiringStorage.get('my-key')).toBeNull();
});

We assert that after exactly 5 minutes we still get the right value. If time progresses any further the value is forgotten and null is returned.

That progressTime() function is not part of Jest. It’s coded up in the tests themselves. Let’s go take a look at it’s implementation.

function progressTime(minutes) {

    const currentTime = (new Date()).getTime();

    const newTime = new Date(currentTime + (minutes * 60000));

    const originalDateClass = Date;

    Date = function (dateString) {
        return new originalDateClass(dateString || newTime.toISOString());
    };
}

We’ll go over this line by line. In JavaScript a way of getting the time is the built-in Date class.

const currentTime = (new Date()).getTime();

We calculate the new time by adding the right amount of milliseconds to it.

const newTime = new Date(currentTime + (minutes * 60000));

Here comes the tricky part. We here going to hold a reference to the original date class. And then we are going to replace the built-in Date with our own function.

const originalDateClass = Date;

Date = function (dateString) {
    return new originalDateClass(dateString || newTime.toISOString());
};

This is called monkey patching. The original Date is replace by a function that, given no parameters, it won’t return the actual time, but the time we gave it (newTime). Cool! Btw you can’t do it this in PHP, which is a shame because it would make testing stuff a lot easier.

Now that you understand how progressTime works let take a look at these two tests from inside tabs.test.js.

it('opens up the tabname found in storage', async () => {
    expiringStorage.set('vue-tabs-component.cache.blank', '#third-tab', 5);

    const tabs = await createVm();

    expect(tabs.activeTabHash).toEqual('#third-tab');
});

it('will not use the tab in storage if it has expired', async () => {
    expiringStorage.set('vue-tabs-component.cache.blank', '#third-tab', 5);

    progressTime(6);

    const tabs = await createVm();

    expect(tabs.activeTabHash).toEqual('#first-tab');
});

In the first test we set a value in the expiring storage with a key we know our component will use. Then we let Vue do it’s thing. When it’s ready we assert that the right tab is active.

In the second test we set the same value in the expiring storage, but then we are going to progress time. Finally we assert that the item in expiringStorage is not being used, but that the first tab is being used.

Now you might think that the that last test is a bit unnecessary. To so degree you’re right: with the test of the expiringStorage class itself we already covered the items do in fact expire. But that extra extra component test gives me some extra confidence that the component behaves correctly.


You’ve reached the end of part four. Only one more part to go: Jest awesomeness and skeleton to get started

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band.
  • Hi Freek,
    thank you for your great posts. They were the starting point for me to look also more into Jest. Although, I was having some problems writing tests with Vue Router. Do you have some experiences with setting a windows location and let then the Vue Router render the right component?

    I always get errors when I run the following test:
    https://gist.github.com/alexlanz/c751debbcc8a37c11fd78054e9c615e9

    I don’t want to disturb you too much, but maybe you have some time to take a quick look at the code.