Unit testing with Vue — Approach, tips, and tricks — Part 2

Bruno Teixeira
Pixelmatters
Published in
5 min readNov 20, 2017

Already know which keywords are important before writing some tests? And what about Testing Methodologies? Testing the Vue store maybe? You can check all these topics in Part 1 of this article.

Component Tests — Using Vue instance

A component has many structures that should be tested:

  • Lifecycle hooks: For example, test if a function is called when the component is mounted, updated, …
  • Methods: Test if the function’s return is the expected or if the changes on data were correctly made.
  • Watchers: When changing a prop or data value, check if the watcher is invoked.
  • Computed properties: Check if the computed property is returning the intended value.

Components can have multiple functions that use each other as a dependency and this can turn into some complex behaviors. So, it’s important to:

  • Try to make small, easy to test functions (too many dependencies or complex functions make tests harder to write — and the code harder to read as well).
  • A function should return a value or change a field on the component’s data in order to be easier to test.
  • Control your test environment. Focus on testing a single unit of code, mocking the environment or dependencies invocations. Using shallow instead of mount helps to control how the component’s instance renders. When a component is connected to store (either by mapActions, mapState or mapGetters) it’s really important to mock the store and pass it as a global parameter to the component’s instance.

When a component instance is created, a wrapper around the component instance (also named vm) is created and it provides a great API to edit props, data, and many other properties.

Be aware that avoriaz syntax (which is very similar to vue-test-utils syntax) is used in the following tests.

Testing Lifecycle Hooks

A Vue component has multiple lifecycle hooks and to test those, actions that trigger them must be done (create the instance to test beforeMount and mounted while destroying it will trigger the beforeDestroy and destroyed).

Using this component:

To check if mounted and destroyed hooks work as intended, an instance of this component should be created and destroyed. Since stub can be applied not only to the component’s instance (and therefore the component’s methods) but also to the window, it’s possible to verify if events are being handled as well.

On the test below it’s possible to see how the lifecycle hooks are tested:

Creating spies or stubs on the component (BasicComponent — as you see in the Mounted example) instead of creating them on the instance (wrapper.vm) is useful when you want to mock / spy on a dependency that will be invoked on the instance mount lifecycles (like beforeMount or mounted).

Testing Methods

Methods should always have a clear way of being tested, either by returning a value / Promise, changing a component’s data or controlling other functions invocations. Thinking of how a function will be tested will very probably improve the code’s quality and maintainability.

Many times functions behavior changes depending on a parameter value or other function’s invocation. The context should be mocked (probably using stubs) to test all use cases.

In the above scenario, setObjects function will have a behavior depending on getObjects result. In order to test both use cases, the return value of getObjects invocation should be controlled as seen in this test:

When using a function that depends on an API call, an approach to take is to use a Promise. In this scenarios, the best way to proceed is to return the Promise.

The following component is connected to store. The store should be completely mocked in order to control the test’s context and avoid any kind of errors.

Returning the Promise allows the API call to be stubbed and this way, it triggers the Promise’s resolve. All the assertions should run on the callback.

Considering that getObjectsAction returns a Promise (that will perform the API request on the action), a good way to test this function would be:

This approach (returning a Promise or a chain of Promises) is very useful in scenarios where requests need to be mocked to avoid timeout errors and allowing to test how a function will perform after the Promise’s resolve. Instead of resolving the Promise, reject it will also trigger the catch scenario on the test.

Testing Watchers

A watcher is used to react to data changes and it can be applied to a component’s props or data. To trigger watchers on the test, setData or setProps methods can be used.

Changing propId will trigger the watcher.

This test will only check if consoleLogValue is being invoked with the correct parameter using calledWith as there is no intention to verify how consoleLogValue behaves. That should be done in another test. For this reason, a stub is applied to consoleLogValue.

Testing computed properties

It’s usual to use computed properties to retrieve a value depending on one or multiple props. Computed properties usually represent simple operations that shouldn’t be placed on the template to be easier to maintain.

To access computed properties, we can use the component instance directly (wrapper.vm.computedProperty). That said, setProps can be used to test all the branches:

Component Tests — Using the Object

As explained before, Vue components’ attributes are all functions. This mindset will make it easier to approach the tests we only need to know how is the object composed. Other than that we’re just testing a function, a small unit of code that shouldn’t have many dependencies.

Considering the following component:

In order to test a function that requires values from props or from component’s data (as the Vue instance will not be created now), a variable must be created with this values and passed to the tested function. On the following tests, this variable will be named ‘context’.

Without a Vue instance, invoking functions uses the object’s properties (Object.computed, Object.methods, etc). The ‘context’ variable allows the tested function to still use the expected values from the Vue instance data structure.

The lifecycles hooks can also be invoked using the object’s properties. Operations like using functions created on mapActions can be placed on the ‘context’ variable to avoid mocking the store (one of this approach’s major advantages). Using the following component:

The getInitialConfig function will be placed inside the ‘context’ variable and therefore, a spy is created to understand if the function is called when the created hook is invoked.

Using a more ‘object oriented’ methodology allows to write less code and in some scenarios, reduce the test’s complexity.

Conclusion

Unit testing can sometimes be tricky and if you’re starting, it will surely cause some pain. But in the end, it’s worth it. If you’re still wondering about the importance of testing your Vue app, check this article.

Tests bring great advantages to the project and to the developer’s evolution. The developer’s mindset will change to write better, easier to test and more maintainable code using pure functions, following the Do One Thing (DOT) rule instead of overloading a unit of code with multiple responsibilities and taking better decisions over the structure and scalability of an application.

This article focused on showing some examples of how to approach various Vue.js testing domains, more like an easy to remind / understand how to approach a certain scenario and to help you all understand what it’s being done when writing a test. If any particularly useful scenario was skipped or if you have any suggestion, leave a comment below. Any feedback will be appreciated and don’t forget to 👏 if this article helped you!

--

--

Bruno Teixeira
Pixelmatters

Head of Product @ Pixelmatters 🤙 • Striver, Learning focused and Experiences lover.