codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

Mocking and Stubbing API calls in Vue Apps with Cypress and Jest

--

Cypress.io is a e2e testing framework — image: cypress.io

Almost all single page applications will make many calls to external services. I will discuss how to test API calls, specifically:

  • Unit testing Vuex actions that use axios
  • End to end (e2e) testing using Cypress

The source code for this project is available here.

We will start at the bottom of the test pyramid with some unit tests, and finish up with some e2e tests.

Check out my Vue.js 3 course! We cover the Composition API, TypeScript, Unit Testing, Vuex and Vue Router.

Setup

Install the vue-cli with npm install -g @vue/cli, and then run vue create api-tests. Select "Manually select features" and choose the following:

  • Babel
  • Vuex
  • Unit Testing
  • E2E Testing

For unit testing, we want jest, and for e2e select cypress. After the installation finishes, cd api-tests and install Axios with npm install axios.

Unit Testing Axios with Jest

We will be using jsonplaceholder, a service which simulates a REST api. The endpoint is https://jsonplaceholder.typicode.com/posts/1 and the response looks like this:

We will be doing TDD: write the test, watch it fails, and then make it pass.

We will see how to mock axios in two situations:

  1. A Vuex action, which makes an API call and commits the result
  2. An e2e test, which displays the result in a UI

Let’s start with the action test. Create the test file by running touch tests/unit/actions.spec.js. Before writing any code, run the test and watch it fail with npm run test:unit:

You should get:

Let’s add a test. In actions.spec.js add the following:

Running this with npm run test:unit yields:

As expected, the tests fails. We haven’t even created getPost yet, so let's do so in src/store.js. We will also export it seperately to the default export new Vuex.Store:

Now we can import { actions } in the spec:

This gives us a new error:

store is not defined. The goal of this test is simply to make the API call, and commit whatever response comes back, so we will we mock store.commit, and use Jest's .toHaveBeenCalledWith matcher to make sure the response was committed with the correct mutation handler. We pass store as the first argument to getPost, to simulate how Vuex passes a reference to the store as the first argument to all actions. Update the test:

jest.fn is just a mock function - it doesn't actually do anything, but records useful data like how many times it was called, and with what arguments. The test now fails with different error:

This is what we want. The test is failing for the right reason — a SET_POST mutation should have been committed, but was not. Update store.js to actually make the API call:

Note we added async to the function, we we can use await on the axios API call. The test still fails with same error - we also need to prepend the action call in the test with await:

Now we have two passing tests, including the default HelloWorld spec included in the project:

This is not ideal, though — we are hitting a real network, which makes the unit test slow and prone to failure. Luckily, Jest let’s us mock dependencies, like axios, in a number of ways. Let's see how to do so with jest.mock.

Mocking Axios in the Action spec

Jest provides no less that four different ways to mock classes and modules, In large projects, I use manual mocks by creating a __mocks__ folder on the same level as node_modules and exporting a mock axios module, however for the simple example I will use an ES6 class mock. I think both are fine, and have been tending towards this style as of late.

To mock axios using an ES6 class mock, all you need to do is call jest.mock('axios') and return a function with the desired implentation (since ES6 classes are really just functions under the hood). In this case, we want a get function that returns a userId: 1 object. Update actions.spec.js:

Easy. The test still passes, but now we are using a mock axios instead of a real network call. We should watch the test fail again, though, just to be should, so update the mock to return { userId: 2 } instead:

Looks good — the test is failing for the right reason. Revert the test, and let’s move on to writing an e2e test.

Stubbing Axios in a component lifecycle

Now we know how to test an action uses axios - how about in a component? In preparation for writing an e2e using Cypress, let's see an example of a component that makes an API call in its created hook.

Open src/components/HelloWorld.vue, and delete all the existing markup - you should be left with this:

We want to import axios, and make an API request. The code will be similar to the code in getPost. Lastly, we will render the title of the post.

Run the application with npm run serve. Visiting localhost:8080 should show the post title on the screen:

Let’s update the default test vue-cli gave us in tests/e2e/specs/test.js:

Run the test with npm run e2e. Cypress has a great interface and is really easy to use. You should see:

Cypress UI

Click ‘test.js’. A Chrome browser should open and if everything went well, you should see:

Passing test using a real API endpoint

It works! However, this test suffers from the original problem we had in the unit test we wrote — it is using a real network call. We want to stub the network call, with a fake one, so we can consistently reproduce the same results without relying on a potentially flakey external API. To stub a response in Cypress, you need to do two things:

  1. Start a cy.server
  2. Provide a cy.route

cy.route takes several forms. The one we will use is

cy.route(url, response)

Update the test to use a stubbed response:

If you still have the Cypress server running, saving should automatically rerun the specs. Now we have a failure:

Failing Test

We can see on the right hand side that the stubbed response was rendered! Simply update the spec to assert the stubbed title is rendered and everything should be green again:

Passing Test

Conclusion and Improvements

We saw how to mock axios in a Vuex action spec, and how to stub the response using Cypress. With the advent of tools like Jest and Cypress, testing is extremely simple and actually makes development a lot more smooth one you are in the habit of writing tests.

Some improvements can be made, an are left an exercise:

  • Write some tests using Cypress against a real server, to test critical paths in your application, such as sign up and login. Not stubbing, but against a real server
  • Mock axios using Jest's manual mocks, where you create a __mocks__ folder with a mock implementation of axios for use in your unit tests
  • Write a unit tests for HelloWorld.vue that mocks axios in the same way as actions.spec.js

The source code for this project is available here.

Originally published on Lachlan Miller’s blog.

✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by Lachlan Miller

I write about frontend, Vue.js, and TDD on https://vuejs-course.com. You can reach me on @lmiller1990 on Github and @Lachlan19900 on Twitter.

Responses (2)

Write a response