How to Test Vuex with Jest

I spent a couple of days to figure out how to test Vuex for my app. Probably this is not the best way so let me know when you find better one. I use Nuxt.js and Firebase for my app but I guess there is not a big difference from the case of normal Vue.js app.

Add Jest

In this article, I used Jest to test my app. Let’s begin with installing it.

npm i jest --save-dev

Configure it in package.json.

package.json

{
  "scripts": {
    "test": "jest"
  }
}

Now you can run the test with Jest.

npm run test

Add Vue Test Utils

You need to install some modules to test Vuex.

  • vue test utils – official test library for vue.js
  • lodash.clonedeep – you can copy states
  • npm install @vue/test-utils lodash.clonedeep --save-dev

    Configure Jest & Babel

    If you use special alias for vue.js such as ‘@’ or ‘~’, you need to configure Jest. You also need to configure Babel if you use ‘import’ instead of ‘require’.

    npm install babel-jest 'babel-core@^7.0.0-0' @babel/preset-env @babel/plugin-transform-runtime @babel/core babel-preset-vue-app --save-dev

    package.json

    {
      "scripts": {
        ...
      },
      "jest": {
        "transform": {
          "^.+\\.js$": "babel-jest"
        },
        "moduleNameMapper": {
          "^@/(.*)$": "/$1"
          // The example of '@'. Add config here if you want to use '~' in your test
        }
      },
      "babel": {
        "env": {
          "test": {
            "presets": [
              "@babel/preset-env"
            ]
          }
        }
      }
    }
    

    Mocking Firebase

    If you want to mock firebase, there is a good module called firebase-mock. The configuration is on the next chapter.

    Add Test

    Here is an example of Vuex I use for this article.

    store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    import users from './modules/users'
    import notes from './modules/notes'
    Vue.use(Vuex)
    
    const createStore = () => {
      return new Vuex.Store({
        modules: {
          users,
          notes
        }
      })
    }
    
    export default createStore
    

    store/modules/notes.js

    import firebase from 'firebase/app'
    import 'firebase/firestore'
    import { db } from '@/plugins/firebaseApp'
    
    const state = {
      privateNotes: []
    }
    
    const getters = {
    }
    
    const mutations = {
      addNote (state, { note }) {
        state.privateNotes.push(note)
      }
    }
    
    const actions = {
      async addNote ({ commit, rootState }, { content }) {
        const newNoteDocRef = await db.collection('notes').add({
          content: content,
          userId: rootState.users.currentUser.uid,
          createdAt: firebase.firestore.Timestamp.now()
        })
        const newNote = await db.doc(`notes/${newNoteDocRef.id}`).get()
        commit('addNote', { note: newNote.data() })
      }
    }
    
    export default {
      namespaced: true,
      state,
      getters,
      mutations,
      actions
    }
    

    store/modules/users.js

    import { googleProvider, auth } from '@/plugins/firebaseApp'
    
    const state = {
      currentUser: null
    }
    
    const getters = {
      currentUser (state) {
        return state.currentUser
      }
    }
    
    const mutations = {
      setUser (state, { user }) {
        state.currentUser = user
      },
      initUser (state) {
        state.currentUser = null
      }
    }
    
    const actions = {
      signIn () {
        auth.signInWithRedirect(googleProvider)
      },
      async logout ({ commit }) {
        await auth.signOut()
        commit('initUser')
      }
    }
    
    export default {
      namespaced: true,
      state,
      getters,
      mutations,
      actions
    }
    

    In this example, I tested ‘addNote’ action.

    spec/store/modules/notes.spec.js

    import Vuex from 'vuex'
    import notes from '../../../store/modules/notes'
    import { createLocalVue } from '@vue/test-utils'
    import cloneDeep from 'lodash.clonedeep'
    
    // this is the configuration for mocking firebase (firestore)
    jest.mock('@/plugins/firebaseApp', () => {
      const firebasemock = require('firebase-mock')
      const mocksdk = firebasemock.MockFirebaseSdk(null, null, () => new firebasemock.MockFirestore().autoFlush())
      const firebaseApp = mocksdk.initializeApp()
      return {
        db: firebaseApp.firestore()
      }
    })
    
    const localVue = createLocalVue()
    localVue.use(Vuex)
    
    describe('store/modules/notes.js', () => {
      // You clone the store with some fake data
      let store
      beforeEach(() => {
        // You prepare test store
        const users = {
          state: {
            // I made up currentUser object so that this test can focus on modules/note.js. 
            currentUser: {
              uid: 'dlafgjalfg'
            }
          }
        }
        store = new Vuex.Store({
          modules: {
            notes: cloneDeep(notes),
            users: users
          }
        })
      })
      describe('actions', () => {
        test('addNote', async () => {
          expect(store.state.notes.privateNotes).toEqual([])
          await store.dispatch('notes/addNote', { content: 'abc' })
          expect(store.state.notes.privateNotes).not.toEqual([])
        })
      })
    })
    

    You might get an error like below. It is related to babel and async/await.

    ReferenceError: regeneratorRuntime is not defined

    Following this, you have to configure package.json.

    package.json

    "babel": {
       "env": {
         "test": {
           "presets": [
             [ "@babel/preset-env",
               {
                 "targets": {
                   "node": "current"
                 }
               }
             ]
           ]
         }
       }
     }
    

    Leave a Comment