How to test Nuxt.js asyncData and fetch hooks

When you setup a new project with the official create-nuxt-app or when you do it manually you probably setup a testing framework to write unit tests. Most Nuxt.js and Vue applications use the official @vue/test-utils which includes clever methods and options to help you write the best unit tests for your application. You probably tried to test your asyncData or fetch hook but couldn't succeed. This article explains why this is, but also shows how you can unit test these hooks. 

Testing a Vue component

A basic unit test for a basic Vue component just costs a few lines of code with the test utils package. By (shallow) mounting a Vue component you’re able to test properties, computed properties, and methods. The component is reactive if you execute methods of the component or change some properties. A basic test will look something like;

import { shallowMount } from '@vue/test-utils';
import ExampleComponent from './example-component.vue';

describe('Example component', () => {
  it('should test something', () => {
    const wrapper = shallowMount(ExampleComponent);
    expect(wrapper.vm.anyProp).not.toEqual(anyExpectedValue);
    wrapper.vm.myMethod();
    expect(wrapper.vm.anyProp).toEqual(anyExpectedValue);
  });
});

You mount the component, call a method and check if the Vue component reacts as you expect. 

Testing a Nuxt.js component

Nuxt.js components are almost the same as a Vue component. So, a basic test will look the same in most cases. Nuxt.js adds extra hooks to the Vue component API, such as the asyncData and fetch hook to load data before a component is rendered, but also the head object to add dynamic meta data to any page. Since the test utils package is written for Vue components and not for Nuxt.js components, it lacks support for these extra hooks and methods. That means we can't access these hooks and methods through the wrapper in our test, but we can access the hooks or methods directly.

Call hook directly

import ExampleComponent from './example-component.vue';

describe('Example component', () => {
  it('should test something', async () => {
    await ExampleComponent.asyncData();
    expect(something).toEqual(somethingExpected);
  });
});

This method has a downside. The to be tested component is not mounted, so we can't make assertions on its changed state. We can test return values, check if spied on methods are being called, but we can’t test the component on state changes. Another downside is that inside these hooks during test the `this` object is not referring to the current Vue component, since it is not executed as part of a mounted Vue component.

Call hook directly with correct context

The solution is obvious; we should mount the component, and tell the hook what its current context is. We need to tell the hook that the `this` object should be the Vue component. With Function.prototype.call() method we can execute any method with the `this` as first argument. The syntax of the method is;

call(thisArg, arg1, ... , argN);

That means that if we are able to mount the component, and add the state of that mounted component as first argument to the .call() method that we are able to make assertions on the components state. And it also means that `this` during test executing is the component itself, like it is during runtime. 

import { shallowMount } from '@vue/test-utils';
import ExampleComponent from './example-component.vue';

describe('Example component', () => {
  it('should test something', async () => {
    const wrapper = shallowMount(ExampleComponent);
    expect(wrapper.vm.anyProp).not.toEqual(anyExpectedValue);
    await ExampleComponent.asyncData.call(wrapper.vm);
    expect(wrapper.vm.anyProp).toEqual(anyExpectedValue);
  });
});