For our software, we have chosen AngularJS as our main Javascript framework. But, when it came to develop a new widget for the dashboards, we figured out that AngularJS does not allow to display several apps on the same page. This problem led us to consider the usage of a new framework.

Example of project dashboard where AngularJS is used

We decided to test and choose a new Javascript framework which allows us to include more than one root module in one single page. We choose to try out Angular 4, React and Vue.js. Here are the results of our experimentation:

Angular vs React vs Vue.js

Comparison of javascript frameworks : Angular, React, Vue

After these first tests we took the high road of Vue.js. Indeed, when we took this decision, React was licenced in a category X. React was open source but with too many restrictive usages from our point of view. But now React has been re-licenced to a MIT licence a shirt time later, that should change things…

Introducing a new framework in an existing code base is always a great challenge. To limit the scope, we decided to try it in a new little widget for the dashboards: the widget “Labels”.

Labeled items widget

The “labeled items widget” displays a list of objects referenced by some given labels. For instance, in Tuleap, you can add labels to an element in order to categorize it. For now, only Pull Requests are taggable with labels.

Example of Pull Request list.

Vue.js components

Now, we use Vue.js to display the list of items tagged by the labels. Our web-component is splitted in two Vue.js components having their own behaviors and responsibilities:

A generic LabeledItem component

It receives an object representation containing a title and a .svg icon as properties.

It sanitizes the icon and displays it alongside the title.

A LabeledItemsList component

It receives a project id and a list of labels ids as properties.

It fetches accordingly the first batch of items to display via /projects/{id}/labeled_items REST route.

If there are more than 50 items, a [Load more] button is shown.

If an error is returned from the REST route, it displays it.

If no items are retrieved from the server, it displays a default empty state.

Else, it renders a list of LabeledItems.

Unit testing in Vue.js

What we wanted to test

Whenever we introduce new code, we write unit tests to guarantee non regression. This is what we wanted to check for the label widget:

Components are well rendered. Error messages are well displayed when REST route fails. [Load more] button is displayed only when necessary. The list of items is correctly populated regarding user permissions. Empty state have a custom message when user doesn’t have reading permissions for all items tagged by labels.

Setting up tests

Our front-end build system is based on webpack and we already have a handful of technologies/frameworks (jquery, prototype, AngularJS). Vue.js build must fit in this environment.

We chose not to use `vue-cli` because it generates its own webpack configuration. Vue-cli is great for getting started fast on new projects, from scratch. Here, we wanted to build a small app that is part of a much greater codebase. The configuration vue-cli generated was conflicting with our existing webpack config. It was too cumbersome to adapt all our build system to comply with that config.

In our app, we have tools that are developed in vanilla, so our webpack configuration has always to deal with two types of files: .js and .vue. We also use Karma and reused a basic configuration for it.

Once webpack and Karma are set up, we wrote a first test. Everything worked as expected and as explained in Vue js. documentation.

Handling mocks in Vue.js

When we display the item list, we import an object (rest-querier.js) which calls a REST route to fetch the data. In our tests we need to mock this behaviour as we cannot rely on the full application for unit testing. When testing angular apps, we were used to angular-mocks but Vue.js doesn’t provide an equivalent package.

A simple way to handle mocks, would have been to use Jasmine.spyOn(object, method) but we can’t use it because ES6 makes imports read-only. Hence, we have tested two different solutions.

Vue-loader

Vue.js describes in its doc a way to inject dependencies in tests using vue-loader. Given a special import, it returns a factory object which allows to inject the component’s dependencies as mocks. Example:

What is this weird import? As explained in the documentation, this required expression does two things:

!! at the start means “disable all loaders from the global config”;

vue-loader?inject! means “use vue-loader, and pass in the ?inject query”. This tells vue-loader to compile the component in dependency-injection mode.

The major problem with this method is that it bypasses all the loaders defined in the webpack config. This is not a good thing. As you can see in this Javascript style guide, it’s a bad practice to override loaders during import. We had to find another solution.

babel-plugin-rewire

The second way we’ve found to mock our objects, was to use `babel-plugin-rewire`. This babel module “adds a special setter and getter to modules so you can modify their behaviour for better unit testing”. With this method, imported objects (read-only) are wrapped and can be easily spied. This way, we are able to create Jasmine spies on rewired objects:

In the example above, we declare a Jasmine spy called “getLabeledItems”. Once created, we “rewire” the getLabeledItems function imported by our component LabeledItemsList. As a result, it is the jasmine spy that will be imported in LabeledItemsList. We can now write our tests as usual:

Even if we know that we should never override framework mechanisms, the plugin babel-plugin-rewire appears to be:

the cleaner code writing the less risky solution

Promise handling

The last thing we had to do for our tests, is the promise handling. Our REST backend is called asynchronously, and we need to wait its response before asserting results. In Angular we used `$httpBackend`. This angular service intercepts AJAX calls, and allows to manually setup return values. Given that there is no such service in Vue.js, we need to simulate the backend response ourself. Here is an example how we unit tests asynchronous behaviours:

The only bothering aspect of testing asynchronous behaviours without http request interceptor is that we have to recreate the http response structure accordingly to what we want to test. In the test above, we simulate a REST error by returning a rejected promise.

Conclusion

Despite the limited scope of our development at the moment, the usage of Vue.js was not as easy as we initially expected. The main surprise we had is the limited documentation or community writings around unit testing. The community, even if growing, is still small and maybe some usage/patterns are not popular enough.

To summarize, the main difficulties we encountered were:

linked to our custom webpack configuration

find a good way to inject our mock using ES6 import

the lack of information about Vue.js unit testing

Vue.js community is small, even if it’s growing every day

By no way are we Vue.js experts so maybe we overlooked something important that might have simplified our developments, please let us know if there is another way to do it.

Originally published at www.tuleap.org on October 13, 2017.