vuex-snapshot •
Module to snapshot test vuex actions with jest
Table of contents
Why use snapshot tests for actions?
I hope you are familiar with what jest, vuex and snapshot testing are.
Vuex actions are straightforward to read, and writing tests that are more complex and 10 times longer than the code they cover feels really wrong.
Actions fulfill 3 roles:
- Representation of app logic (conditions & calls of commits\dispatches)
- API for components
- Asynchronous layer for store (as mutations must be sync)
As such we unit test them to make sure that:
- When we change \ add execution path others don't get broken
- Our component API didn't change
vuex-snapshot makes this easy and declarative, even for async actions.
Getting started
Prerequisites
- ✔️ Node 6 stable or later
- ✔️
jest
and,babel-jest
installed (es6-modules imports would be used in examples, butvuex-snapshot
is also output as CommonJS)
Installation
via npm
npm install --save-dev vuex-snapshot
via yarn
yarn add --dev vuex-snapshot
Basic example
Say, you are testing some card game
// @/store/actions.jsconst restartGame = { } // actions.spec.js /*__snapshots__/actions.spec.jsafter running jest*/ // Jest Snapshot v1, https://goo.gl/fbAQLP exports`play restartGame matches snapshot 1` = `Array [ Object { "message": "COMMIT: shuffleDeck", }, Object { "message": "COMMIT: setScore", "payload": 0, },]`;
NOTE: by default vuex-snapshot would not use commit & dispatch from your store, but you can pass them via mocks
Usage
Testing async actions
// @/store/actions.jsconst openDashboard = { } // actions.spec.js
Testing async actions [2]
// @/store/actions.jsconst login = { return { if!gettersuserloggedIn else }} // actions.spec.js // testing error scenarios is just as easy
NOTE: promises with same names would be matched to resolutions in order they were created
mocks
By using mocks object you can pass state, getters, payload(action's second argument) of any type,
as well as custom commit
and dispatch
functions.
NOTE: Make sure your getters are what they return, not how they calculate it
Example
const action = jest const mocks = payload: 0 state: stateValue: 'smth' getters: answer: 42 commit: consolelog dispatch: jest // would call the action like
Proxies is an object with commit and dispatch that were actually passed to action (not those from mocks)
Note: state and getters are being reassigned. Like they would pass
.toEqual
test, but not a.toBe
one.
MockPromises
const name = 'some string'const cb = {}cb namecb // name will be 'Promise'name //cb will be () => {} // some manual controlconst toResolve = 'some name'const toReject = 'some other name'const payload = type: 'any' toResolvetoReject console // some other name
This class extends Promise, so Promise.all and other promise methods work perfectly for it
NOTE:
new MockPromise.then(cb)
actually creates newMockPromise
(that is default Promise behavior). As such there is a risk ofresolutions = ['Promise', 'Promise']
matching this one instead of the Promise you've meant. This is just as true forcatch
,finally
,Promise.all
andPromise.race
snapAction overloads
// where snapshotToWriteTo is instance of Snapshot class
If action returned a promise snapAction
would do the same.
That promise will resolve with an Array
of Object
s that represents action's execution.
It could be compared to snapshot, or tested manually.
If vuex-snapshot experienced internal error snapAction test it would reject with an Object
of following structure:
err // Actual error that has been thrown run // action's execution up to the error point
If action returned anything that is not a promise (including undefined
) snapAction
would
synchronously return an array mentioned above.
Utilities
// all vuex-snapshot Utilities
reset
Reset calls all other resets and useReal.
resetTimetable
Makes sure no already created promises could be matched to resolutions.
resetConfig
Resets vuexSnapshot.config
to default values.
useMockPromise
Replaces window.Promise
(same as global.Promise
) with vuexSnapshot.MockPromise
that could be named and resolved manually.
seRealPromise
uSets window.Promise
to its original value.
useMockFetch
Replaces window.fetch
(same as global.fetch
) with vuexSnapshot.MockPromise
that could be named and resolved manually.
useRealFetch
Sets window.fetch
to its original value.
Config
These fit very specific types of tests, so using
beforeEach(vuexSnapshot.reset)
is highly encouraged.
vuexSnapshot.config.autoResolve
Default
false
Description
Instead of acting according to passed resolutions vuex-snapshot will automatically trigger resolve on each mock promise in order they were created.
vuexSnapshot.config.snapEnv
Default
false
Description
Starts snapshot with 2 entries:
message: 'DATA MOCKS'payload:state //value of stategetters // value of gettersmessage: 'ACTION MOCKS'payload // passed action payload if there was one// values of state, gettes and payload are not being copied
vuexSnapshot.config.allowManualActionResolution
Default
false
Description
Allows vuexSnapshot to resolve promise returned by action.
Tips
Mocking timers for vuex-snapshot resolutions
NOTE: This is not fully accurate simulation because resolving it manually or via resolutions would cause a bit higher priority in event-loop, and resolution on timeout would be 1 tick late Because Promise.then() is not synchronous
Deep testing (execute called actions)
// @/store/actions.jsconst action1 = { } const action2 = { } // actions.spec.js
This should work for async actions too