Sampath Gajawada I'm a full-stack developer who always wishes to implement new and challenging elements in my daily life. Currently, my focus is on React and Vue.

Unit testing Vuex modules with Jest

4 min read 1156

Unit Testing Vuex Modules Web Nocdn

If you’re building a medium to large-scale SPA, chances are you’ll run into situations where you want to better handle the state of your Vue components.

In any application, multiple components depend on the same piece of state. Let’s imagine that multiple actions from different components would like to mutate the same state. To overcome these challenges, Vuex helps us to maintain state across the application.

In this article, I’ll guide you through implementing a Vuex module in TypeScript, then unit testing it using Jest. The complete code for this tutorial is available at the vuex-test GitHub repository; feel free to fork it. Let’s get started!

What is Vuex?

Vuex is a state management pattern and library for Vue applications that allows you to use centralized state management in your applications, helping you to take advantage of Flux-like architecture. The Vuex store contains four core concepts:

  1. State
  2. Getters
  3. Mutations
  4. Actions

The state object contains the data you want to have in the store, including all your application-level state, serving as the single source of truth. The properties defined in the state can be any data type, including a string, number, object, or array.

If you’d like to have a derived state based on the store state, for example, counting the list of items, filtering the collection, or using the same set of derived state in other modules or components, you can define getters.

On the other hand, mutations are the only way we can change the state. Mutations are always synchronous, and the payload is optional. You can call a mutation via the commit, i.e., MUTATION_NAME or payload. It’s always recommended to call mutations from actions.

Actions can perform asynchronous operations and commit the mutations. Action handlers receive a context object that exposes the same set of methods or properties on the store instance.

You can use context.getters and context.state to get the state and context.commit to call mutations. You can call action handlers using action-name and payload, and they are called from other actions within the store.

Vuex Modules Diagram
Vuex architecture

Create a Vuex module

As your application size increases, your store can become bloated. To prevent this, Vuex allows you to split the store into modules. Each module can contain its own state, getters, mutations, and actions.



As an example, let’s create an application for managing a to-do list. First, create a new module for to-do operations, which is responsible for getting all the to-do items and updating the state as needed.

Our goal is to build the module for medium to large-scale applications, therefore, it is better to split the mutation types, actions called functions, and the module implementation into separate files:

  • mutation-types.ts: Contains all the function names
  • actions.ts: Responsible for all asynchronous operations
  • index.ts: The module implementation
import { IToDo } from '@/types/todo';
import {Module, VuexModule, Mutation, Action} from 'vuex-module-decorators';
import TodoActions from './actions';
import * as mutationTypes from './mutation-types';

@Module({namespaced: true, name: "Todos"})
export class ToDoModule extends VuexModule {
  todos:Array<IToDo> = [];
  loading = false;
  get completedTodos(){
    return this.todos.filter((todo:IToDo)=> todo.completed);
  }
  @Mutation
  [mutationTypes.ON_FETCH_TODOS_STARTED]() {
    this.loading = true;
  }
  @Mutation
  \[mutationTypes.ON_FETCH_TODOS_SUCCESS\](data: Array<IToDo>) {
    this.loading = false;
    this.todos = data;
  }
  @Mutation
  [mutationTypes.ON_FETCH_TODOS_FAILED]() {
    this.loading = false;
    this.todos = [];
  }

  @Action({rawError: true})
  public async fetchTodos():Promise<void> {
      try {
          this.context.commit(mutationTypes.ON_FETCH_TODOS_STARTED);
          const response: Array<IToDo> = await TodoActions.fetchTodos();
          this.context.commit(mutationTypes.ON_FETCH_TODOS_SUCCESS, response);
        } catch (error) {
          this.context.commit(mutationTypes.ON_FETCH_TODOS_FAILED);
        }
  }

}

The code snippet above contains the following implementation:

  • fetchTodos Action: Fetches the to-do items from the REST API and commits the mutations
  • ON_FETCH_TODOS_STARTED mutation: Updates the loading state attribute
  • ON_FETCH_TODOS_SUCCESS mutation: Updates the todos state array
  • ON_FETCH_TODOS_FAILED mutation: Resets the todos and updates loading as false
  • completedTodos getter: Gets only the to-do items that are completed

Initialize tests

We’ll use the Jest framework for unit testing; Jest is simply a JavaScript testing framework that can be easily installed with any node-based package manager, like npm or Yarn. There are few advantages of using Jest, for example, Jest tests run in parallel, include built-in code coverage, and support isolated tests, mocking, and snapshot testing.

You can initialize the test by creating a store, attaching Vuex to Vue, and registering the store. localVue is the scoped Vue constructor that we can change without affecting the global Vue constructor. The code snippet below will initialize the store:

describe('Todos Module', function() {
    let store: any;
    let todosInstance: ToDoModule;

    beforeEach(function() {
      localVue.use(Vuex);
      store = new Vuex.Store({});
      registerStoreModules(store);
      todosInstance = getModule(ToDoModule, store);
    });

    it('should exists', function() {
      expect(todosInstance).toBeDefined();
    });
});

Testing actions

In the todos module, we created the fetchTodos action, which fetches data from a REST API and fills the state using mutations. Since the REST API is an external call, we can mock it using a Jest function, then validate whether it is being called and the state is being updated:

it('fetchTodos action should fill todos state', async function() {
      // arrange
      const todosMocked = todos as Array<IToDo>;
       // act
      jest.spyOn(TodoActions, 'fetchTodos').mockImplementation(
        (): Promise<Array<IToDo>> => {
          return Promise.resolve(todosMocked);
        }
      );
      await todosInstance.fetchTodos();
      // assert
      expect(todosInstance.todos.length >0).toEqual(true);
      expect(TodoActions.fetchTodos).toHaveBeenCalled();
});

Testing getters

Getter functions simply return the state object. In our example, we have one getter function, completedTodos, which should return the to-do items that are completed:

  it('completedTodos getter should return only completed todos', async function() {
      // arrange
      const completedTodos = todosInstance.completedTodos;
      // assert
      expect(completedTodos.every((todo:IToDo)=> todo.completed)).toEqual(true);
    });

Testing mutations

As we already know, mutations are the only way to change the state. We can test the ON_FETCH_TODOS_SUCCESS mutation by sending mock to-do tasks and validating whether the state is modified.

The code snippet below is for the success mutation. The same applies for the started and error mutations too:

it('ON_FETCH_TODOS_SUCCESS mutation should update given todos',  function() {
      // arrange 
      const todosTest = [
        {
          userId: 13,
          id: 12,
          title: "Move to new city",
          completed: false
        },
        {
          userId: 15,
          id: 21,
          title: "Finish a novel",
          completed: true
        },
      ];
      // act
      todosInstance.ON_FETCH_TODOS_SUCCESS(todosTest);
      // assert
      expect(todosInstance.todos.length).toEqual(2);
      expect(todosInstance.todos).toEqual(todosTest);
    });

Conclusion

In this tutorial, we learned about Vuex by creating and unit testing a Vuex module with TypeScript and Jest. We covered the four core concepts of a Vuex store, including state, getters, mutations, and actions. With Vuex’s centralized state management, you can simplify your application and take advantage of Flux-like architecture.

I hope you learned something new, and be sure to leave a comment if you have any questions. Happy coding!

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Sampath Gajawada I'm a full-stack developer who always wishes to implement new and challenging elements in my daily life. Currently, my focus is on React and Vue.

Leave a Reply