Skip to content

Instantly share code, notes, and snippets.

@egardner
Last active March 17, 2018 17:07
Show Gist options
  • Save egardner/5853a75617056d9035ec18180a5bd114 to your computer and use it in GitHub Desktop.
Save egardner/5853a75617056d9035ec18180a5bd114 to your computer and use it in GitHub Desktop.
Vuex action test examples

Testing actions in Vuex

In Vuex, Actions are analagous to Reducers in Redux. They are functions which can contain asynchronous operations, but they do not manipulate any state in the store directly. Instead, they can only commit mutations, which are synchronous functions that replace an existing store value with a new one.

Since Actions can potentially contain some complicated logic (like deciding which mutation or series of mutations should be committed), they are a good candidate for unit tests. Here is an example of how to do this following the suggestions in the Vuex docs.

Testing Strategy

Actions commit mutations rather than returning values or directly modifying the store, so we will write tests to ensure that an action commits the correct sequence of mutations from a starting state.

The Vuex documentation provides an example helper function for this purpose.

Example Files

These examples are taken from a project currently in production, a user interface for a hotel booking service. Users have the ability to view a calendar of available dates for a trip. Clicking on days in the calendar updates the global date selection state which is used for a variety of other features (calculating length of stay, total price, etc).

/**
* Booking.js
*
* This file represents a module in the larger Vuex store for this application.
* Everything relating to global booking state (selected dates, updating selections,
* etc.) lives here. Abbreviated for clarity.
*/
// DateService handles date comparison, etc
import DateService from '@/services/DateService.js'
const state = {
// All dates should be handled here as strings in YYYY-MM-DD format.
dates: {
start: null,
end: null
}
}
const mutations = {
/**
* Updates the dates.start value. Strings should be in YYYY-MM-DD format.
* @param {object} state
* @param {string} date
*/
updateStartDate (state, date) {
state.dates.start = date
},
/**
* Updates the dates.end value. Strings should be in YYYY-MM-DD format.
* @param {object} state
* @param {string} date
*/
updateEndDate (state, date) {
state.dates.end = date
},
/**
* Clears both start and end dates by setting them to null
* @param {object} state
*/
clearDates (state) {
state.dates.start = null
state.dates.end = null
}
}
const actions = {
/**
* Updates the date selection based on the following rules.
*
* - If neither start date nor end date are set, updates the start date.
* - If start date is set and end date is not:
* - if new selection is after the start date, update the end date
* - if new selection is not after the start date, replace the start date
* with the new selection.
* - If both dates are set, clears them and updates the start date.
*
* @param {object} context (commit, state)
* @param {string} newSelection
*/
updateDateSelection ({ commit, state }, newSelection) {
if (!state.dates.start && !state.dates.end) {
commit('updateStartDate', newSelection)
} else if (state.dates.start && !state.dates.end) {
if (DateService.isLaterThan(state.dates.start, newSelection)) {
commit('updateEndDate', newSelection)
} else {
// TODO: should the old start date be set to new end date?
// let oldStartDate = state.dates.start
commit('updateStartDate', newSelection)
}
} else if (state.dates.start && state.dates.end) {
commit('clearDates')
commit('updateStartDate', newSelection)
}
}
}
export default {
state,
mutations,
actions
}
import booking from '@/store/modules/booking.js'
import testAction from '../../helpers/testAction.js'
describe('Booking module', () => {
it('... other tests here')
describe('actions', () => {
describe('updateDateSelection()', () => {
it('updates the start date if neither the start date nor end date are set', () => {
let state = {dates: {start: null, end: null}}
let newStartDate = '2018-03-07'
testAction(booking.actions.updateDateSelection, newStartDate, state, [
{ type: 'updateStartDate', payload: newStartDate }
], () => {})
})
it('updates the end date if the start date is set but the end date is not', () => {
let state = {dates: {start: '2018-03-07', end: null}}
let newEndDate = '2018-03-20'
testAction(booking.actions.updateDateSelection, newEndDate, state, [
{ type: 'updateEndDate', payload: newEndDate }
], () => {})
})
it('clears both dates and updates the start date if both dates are set', () => {
let state = {dates: {start: '2018-03-07', end: '2018-03-20'}}
let newDate = '2018-03-25'
testAction(booking.actions.updateDateSelection, newDate, state, [
{ type: 'clearDates', payload: null },
{ type: 'updateStartDate', payload: newDate }
], () => {})
})
it('updates start date again if the second date is prior to the current start date', () => {
let state = {dates: { start: '2018-03-07', end: null }}
let newDate = '2018-03-06'
testAction(booking.actions.updateDateSelection, newDate, state, [
{ type: 'updateStartDate', payload: newDate }
], () => {})
})
})
})
})
// Test Helper for Vuex Actions
const testAction = (action, payload, state, expectedMutations, done) => {
let count = 0
// mock commit
const commit = (type, payload) => {
const mutation = expectedMutations[count]
try {
expect(mutation.type).to.equal(type)
if (payload) {
expect(mutation.payload).to.deep.equal(payload)
}
} catch (error) {
done(error)
}
count++
if (count >= expectedMutations.length) {
done()
}
}
// call the action with mocked store and arguments
action({ commit, state }, payload)
// check if no mutations should have been dispatched
if (expectedMutations.length === 0) {
expect(count).to.equal(0)
done()
}
}
export default testAction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment