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
- Testing async components — Vue test utils
- About Tasks, microtasks, queues and schedules — jakearchibald.com
- Flush Promises — github
Karthik is a passionate Full Stack developer working primarily on .NET Core, microservices, distributed systems, VUE and JavaScript. He also loves NBA basketball so you might find some NBA examples in his posts and he owns this blog.