Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

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

Original – by Freek Van der Herten – 6 minute read

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

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

What are your thoughts on "Testing a Vue component part 4: more testing and faking time"?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.