How to Mock an AXIOS call with Jest in vue tests utils

Mocking an axios API call in Jest with vue test utils

In this post, we will see how to mock an Axios call with Jest in vue-test-utils library.

For this article, let’s create a Posts.vue component which will call the JSONPlaceholder’s /posts API. The /posts API will return an array of objects. Each object in the array is a post with id, title and body.

Here is our posts component

<template>
  <ul v-if="posts && posts.length">
    <li v-for="post of posts" :key="post.id">
      <p>
        {{ post.id }}: <strong>{{ post.title }}</strong>
      </p>
      <p>{{ post.body }}</p>
    </li>
  </ul>
</template>

<script>
import axios from "axios";

export default {
  name: "Posts",
  data() {
    return {
      posts: []
    };
  },

  async created() {
    const response = await axios.get(
      `http://jsonplaceholder.typicode.com/posts`
    );
    this.posts = response.data;
  }
};
</script>

So, in the created hook we will make a call to the REST API and assign the response data to the posts variable. If we run the app, it should work fine.

Let’s mock the API call.

Mocking an AXIOS call

Calling axios.get will return a promise and will resolve to either a response object or an error object.

To mock the get call on axios in our component, we have to resolve the promise and set up our custom response data in the test file (*.spec.js).

jest.mock("axios", () => ({
  get: () => Promise.resolve({ data: [{ val: 1 }] })
}));

We should add the above snippet before we write anything to test.

Let’s write our test now to see check the data in posts.

jest.mock("axios", () => ({
  get: () => Promise.resolve({ data: [{ val: 1 }] })
}));

describe("Posts.vue", () => {
  it("mocking the axios call to get posts should work", () => {
    var wrapper = shallowMount(Posts);
    expect(wrapper.vm.posts.length).toBe(1);
  });
});

The wrapper object contains events, data, and everything related to the component. The vm on the wrapper will have the data in the posts variable.

We will assert the length of the posts to be 1. But, if we run the above tests, it will fail.

 Expected: 1
 Received: 0

Asynchronous behavior caused the test to fail

The test failed because our mocked promise has not resolved by the time we assert the data in the posts.

There are two ways to fix this. (If you know any other way around, please leave it in the comments)

1. using a $nextTick or setTimeout to assert

If we assert in the $nextTick or in setTimeout callbacks then the test will succeed.

it("mocking the axios call to get posts should work", () => {
    var wrapper = shallowMount(Posts);
    wrapper.vm.$nextTick(() => {
      expect(wrapper.vm.posts.length).toBe(1);
    });
  });

This is because $nextTick schedules a micro task (runs after the function or program that created exits), and by the time it executes our callback code, the promise is resolved.

A similar thing happens for the setTimeout, except that setTimeout queues a task. This task will be executed after it’s time out is complete and after the microtask is completed. So, we will have the promise resolved by the time we get to the setTimeout callback code.

So, asserting in a setTimeout or $nextTick would pass the test as we will have our promise resolved and the posts variable is loaded with the response data.

If you want to understand more about micro tasks, tasks, queues, etc. Read this article.

2. using flushPromises library

The flushPromises library resolves all the pending promise handlers. This is an alternative to using setTimeout or $nextTick.

First, we have to install the flushPromises library with the following command in NPM:

npm i flush-promises

After installing, import the library into the tests and call flushPromises function before asserting.

import { shallowMount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import Posts from "@/components/Posts";

jest.mock("axios", () => ({
  get: () => Promise.resolve({ data: [{ val: 1 }] })
}));

describe("Posts.vue", () => {
  it("mocking the axios call to get posts should work", async () => {
    var wrapper = shallowMount(Posts);
    await flushPromises();
    expect(wrapper.vm.posts.length).toBe(1);
  });
});

Be sure to mock what we expect while mocking the axios calls

Technically, you can mock anything as the return value of the axios call and assert what you’ve set as mock data.

In this blog post, we have taken the example of blog posts from Json placeholder’s API. The API will return an array of objects. Each object will have an id, title, and body.

The typical response would be

[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "abc"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "xyz"
  }
]

But, in our test (s) we mocked our Axios call to return an array of a single item with val property.

Our created hook does not have any logic in it. So, the tests worked out. But, if the code has anything to do with the response from Axios then our tests may have failed. Or instead of checking for the data in the posts variable, if we assert the rendered values, then our test would have failed.

So, better mock with the data you expect from the axios response.

References