Testing Vuex actions with Mirage JS
May 13, 2020
mirage js is a mocking library that allows you to build front end applications without an existing backend the documentation is an excellent place to start. Using Mirage JS to test your Vuex actions makes things easier. No mocking libraries such as axios which gives more flexibility when it comes to testing API responses such as Internal server errors or Bad Requests. I recently set up a project with Mirage JS & decided to do a write up on it.
Create Server
We need to create a Mirage JS server, this will be where we set up our routes & responses when we’re using the App itself. We’ll also use it when writing tests. the documentation covers this better then i can. I’ve included an example below which is from the docs site.
// src/server.jsimport { Server, Model } from "miragejs"export function makeServer({ environment = "development" } = {}) {let server = new Server({environment,models: {user: Model,},seeds(server) {server.create("user", { name: "Bob" })server.create("user", { name: "Alice" })},routes() {this.namespace = "api"this.get("/users", (schema, request) => {console.log('Dumping Axios Request: %o', request);return schema.users.all()})},})return server}
The Action
Here is the action we will be testing which makes an API call that adds the response to the state
import axios from 'axios';/* eslint no-shadow: ["error", { "allow": ["state"] }] */const getters = {};const mutations = {LOAD_USERS(state, users) {state.users = users;}};const actions = {async getUsers({ commit }) {try {// Ideally this would be in an API file// For this purpose this will be fine.const response = await axios.get('api/users');commit('LOAD_USERS', response.data);} catch (error) {throw new Error(`API ${error}`);}}};const state = {users: [],};export default {namespaced: true,state,getters,actions,mutations,};
Writing the tests
I will be using Jest for my test runner & below is what our test will look like. We’ve imported our store ( which uses the module above ), the Mirage JS server & a Mirage JS response which allows us to return Responses when needed.
import { Response } from 'miragejs';import store from '../../src/store/modules/users';import startMirage from '../../src/server';let server;beforeEach(() => {server = startMirage();});afterEach(() => {server.shutdown();});describe('user actions', () => {it('can GET /users', async () => {// Our mock data we expect to get back from the api call.const users = [{ id: 1, firstName: 'James', lastName: 'Smith' },{ id: 2, firstName: 'Jim', lastName: 'Jones' },];// Setup Mirage Server to return array of users// When axios hits /api/users.server.get('/users', () => users);// Mock the 'COMMIT" being passedconst commit = jest.fn();// Call Our Method Under Testawait store.actions.getUsers({ commit });// Make Assertionexpect(commit).toHaveBeenCalledWith('LOAD_USERS', users,);});});
We’re calling our method under test which is our action then asserting that the action successfully calls the mutation
with our mocked data. Axios will make a real request for /api/users
which is when Mirage JS will mock it on the network layer.
Here is an example of testing how our action handles a 500 Server error.
Mirage JS also lets you test for other conditions such as timeouts, bad requests & much more.
it('GET /users gracefully handles API Error', async () => {// Setup mirage to return 500 Error when we hit GET /api/usersserver.get('/users', () => new Response(500, { some: 'header' }, { errors: ['Server Error'] }));// Call Method Under testtry {await store.actions.getUsers({ commit: jest.fn() });} catch (error) {// Make Assertionexpect(error.message).toMatch('API Error: Request failed with status code 500');}});