Abstracting Vuex/Redux Action patterns

Avoid typing the same trivial tests and implementations again and again

Alberto Gualis
Coding Stones
3 min readNov 19, 2017

--

In a context of unidirectional dataflow implementations (like Redux or Vuex) is very common to see patterns like Request/Success/Error in async actions. Let’s see an example to understand this pattern (I’m going to use the Vue/Vuex implementation but you could apply the same concepts in any unidirectional ecosystem).

Fetching a list of films

We want to build a catalog with the movies of the incredible Studio Ghibli. They have this public API that we are going to call from a component using a Vuex action (RETRIEVE_FILMS action).

  • We want to show a loading spinner to indicate that the request has started (FETCH_FILMS_REQUEST mutation).
  • When the request finishes with success, we want to update the store with the received list of movies (FETCH_FILMS_SUCESS mutation).
  • When the request fails we want to update the store to show an error (FETCH_FILMS_ERROR).

This is a very typical pattern but it’s not mandatory to use it. It will depend on our needs (for example, we could decide to have the loading state in the local scope of a component) but it’s very probable that we start using it again and again.

Testing the action

We are using Jest doubles to create a spy (commitSpy) that we will use to verify that Vuex called expected mutations.

Testing action with REQUEST/SUCCESS/ERROR pattern

Let’s see the implementation of the action (we came to this applying TDD, of course):

Action implementation

Note that we are using a factory function (retrieveFilmsAction) to create an object with the run method. Then we use buildRetrieveFilmsAction to inject the retrieveFilms function, which performs the query to the the API. And this is how we declare the action in Vuex store:

Well done! The next step would be to implement the 3 mutations using TDD. It is low hanging fruit so I won’t show it here 🍇🍇 :)

More actions are coming…

We keep adding features to the app. Now we want to retrieve a list of people, places, and things found in the worlds of Ghibli. For that, we will need three new Vuex actions: RETRIEVE_PEOPLE, RETRIEVE_PLACES and RETRIEVE_THINGS.

Each new action will have its respective mutations:

  • RETRIEVE_PEOPLE -> FETCH_PEOPLE_REQUEST, FETCH_PEOPLE_SUCESS, FETCH_PEOPLE_ERROR
  • RETRIEVE_PLACES -> FETCH_PLACES_REQUEST, FETCH_PLACES_SUCESS, FETCH_PLACES_ERROR
  • RETRIEVE_THINGS -> FETCH_THINGS_REQUEST, FETCH_THINGS_SUCESS, FETCH_THINGS_ERROR
Can you see some duplication? Why not extracting some abstraction?

Did you guess? Yes! We can avoid a lot of boring test and production code if we extract the REQUEST/SUCCESS/ERROR management to a generic function (queryAction):

Generic implementation of the REQUEST/SUCESS/ERROR pattern

And the generic test:

With this generic tests we are covering all future actions following the same pattern

Do you get the point? Now we only need to implement our new actions like this:

We could even go a step forward and automatically add “FETCH_” prefixes and “REQUEST”, “SUCCESS”, “ERROR” sufixes.

Now, we won’t need to test the new actions following this pattern. (Of course, we will test when a component calls them in an integration test, but thats another story…).

Hooray! We avoided many boilerplate so we can practice some sport instead of writing boring code ;)

Conclusion

Nothing prevent us from applying in frontend the same design principles that we are used to apply when we work in the backend but, sometimes we blindly follow the documentation examples and forget about it. Do you have some tricks like this one to share with us? Please do it! 🙏🏼

At the Coding Stones we help companies developing software and we also provide training and mentoring. If you liked this post and you want more, contact us, maybe we can play together with your band 🤘🏼🤘🏼

--

--