Taylor Goodall

moon indicating dark mode
sun indicating light mode

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.js
import { 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 passed
const commit = jest.fn();
// Call Our Method Under Test
await store.actions.getUsers({ commit });
// Make Assertion
expect(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/users
server.get('/users', () => new Response(500, { some: 'header' }, { errors: ['Server Error'] }));
// Call Method Under test
try {
await store.actions.getUsers({ commit: jest.fn() });
} catch (error) {
// Make Assertion
expect(error.message).toMatch('API Error: Request failed with status code 500');
}
});

software Engineer, living in Adelaide, Australia Interested in building engaging products and solving problems