Testing Vue Applications With Interactions Involving Asynchronous Operations

My recent series of posts on testing Vue.js applications using Feature Specifications is progressing nicely. In this segment, I will show you how you can test Vue properties and use asynchronous operations in your tests.

Starting from where we left off in our last post, we will implement the login function using a REST API client attached as a Vue property. For the purposes of this document, that API client will actually be a stubbed implementation, but you could use Axios or a library generated by the OpenAPI generator tool.

First, let's attach our library to the global Vue instance by modifying the main.js script.

Now, let's say that our API client has a method through which we can send credentials and receive an authentication token back, this method will be called userAuth. userAuth takes 2 parameters: An object containing the username and password, and a callback which handles the results of the REST request. The callback takes 3 parameters: error: Any error message returned, data: The authentication token as a string, and response: The HTTP response object. Now that we know the semantics of the API, let's write our tests.

Since the last blog post, we have added a new Scenario to our feature file. This scenario indicates we will add mocks for the Vue router and mocks for the API Client. We will then insert these mocks into our mounted instance of the component under test. Next, we will enter credentials and click the Login button. Finally, we will assert that the values were stored properly, that the login method was called, and that the router was told to navigate to the appropriate page after a successful login.

Let's note a few specifics about this test before we move on to write our implementation. In the first given clause, you will see that we used jest's function mocking to create a userAuth method on the mock API client. In the second given clause, we use the same method to mock the push method of the Vue router. In the final given clause, we add those mocked objects to our mounted component. The when and then clauses are pretty standard except for the last then clause. In the last then clause, we use async and await to account for the asynchronous nature of the API callbacks. Calling wrapper.vm.$nextTick() returns a Promise which the await will cause await to block until it is resolved by processing the next tick of the Vue framework. At this point, the callback and the router.push() will be resolved and we can make assertions against those calls.

How does this implementation look? It's not terribly difficult in fact.

A few things to note in the implementation. First, we implement the username and password fields using q-input and use v-model to two-way bind those to their respective properties in the component. Second, we implement the button using q-btn and create a listener for the click event to call the login method. Next, the login method calls the API client and includes a callback function which is used to process the result of the API call. Finally, you will notice that we created an instance of q-alert with a v-if which will show if the call to the API fails and the $data.failedLogin is set to true. Since we are using a callback, we have to use the wrapper.vm.$nextTick() call in our test so that the callback has a chance to resolve.

It's important to note that attaching the API client instance to the global Vue object is NOT the idiomatic way to handle events across components in Vue, but for smaller projects where Vuex might be overkill, it's a reasonable solution. I'll show you more about how to use and test Vuex in forthcoming posts to this series. Take care and happy Vue'ing!!!


Popular Posts