ddraper

A Better way of Writing and Testing Components?

Blog Post created by ddraper Employee on Dec 28, 2016
This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

 

At the end of my last blog post I had got some initial unit tests working using Karma for my Vue.js client running on a Node.js stack. One of the great things about Aikau is the automated testing that we setup in an attempt to prevent us introducing regressions into our code. The main problem that we now have is that a full regression test on 2 browsers takes around an hour to complete. Whilst this is a reasonably manageable amount, it is not ideal for continuous integration testing where ideally you want to capture bugs as soon as they're introduced.

 

The other thing that I wish I had done differently was to create a greater separation between the "pure" JavaScript code and the code that was tied to the working with the browser DOM. In theory it should only be necessary to test web application code on different browsers where the code may actually behave differently - this would typically be when it was working with the DOM to build the interface that is used. It is of course true that different browsers have different JavaScript engines, but typically there shouldn't be a huge amount of difference - especially if you're coding to the lowest common denominator. 

 

There is a good example of this in the breadcrumb component that I am building. In the Vue.js component we are "watching" (i.e. responding to changes on) the relative path attribute. When a change to the value occurs the component builds a new breadcrumb trail from this updated path:

watch: {
   relativePath: function() {
      let lastPathElement = '/';
      this.breadcrumbs = [{
         label: 'Home',
         relativePath: lastPathElement
      }];
      this.relativePath
         .split('/')
         .filter(function(name) {
            return name.trim() !== '';
         })
         .forEach(function(pathElement) {
            let relativePath = lastPathElement + pathElement + '/'
            this.breadcrumbs.push({
               label: pathElement,
               relativePath: relativePath
            });
            lastPathElement = relativePath;
         }, this);
   }
},

 

This function is highly unlikely to behave any differently when run in Chrome, Firefox or Internet Explorer (especially since it's getting transpiled through Babel to ES5). As it is currently written it would have to be tested with multiple permutations on multiple browsers and because it is embedded within a Vue.js component it would also be necessary to drive the tests through the browser and handle all the associated waiting around for the DOM to be updated, etc. 

 

A much better solution would be to abstract this function to a separate module that could be much more easily tested. If it was placed in a module like this:

export default class Breadcrumb {
   static createBreadcrumbs(input) {
      let lastPathElement = '/';
      let breadcrumbs = [{
         label: 'Home',
         relativePath: lastPathElement
      }];
      input.relativePath
         .split('/')
         .filter(function(name) {
            return name.trim() !== '';
         })
         .forEach(function(pathElement) {
            let currRelativePath = lastPathElement + pathElement + '/';
            breadcrumbs.push({
               label: pathElement,
               relativePath: currRelativePath
            });
            lastPathElement = currRelativePath;
         });
      return {
         lastPathElement: lastPathElement,
         breadcrumbs: breadcrumbs
      };
   }
}

 

...it could then be used in the component like this:

watch: {
   relativePath: function() {
      let breadcrumbData = BreadcrumbUtil.createBreadcrumbs({
         relativePath: this.relativePath
      });
      this.breadcrumbs = breadcrumbData.breadcrumbs;
   }
},

 

And have a much simpler tests written for it like this:

describe('Breadcrumb Util', () => {
   it('should generate home breadcrumb for root', () => {
      let breadcrumbs = BreadcrumbUtil.createBreadcrumbs({
         relativePath: '/'
      });
      expect(breadcrumbs.breadcrumbs).to.have.lengthOf(1);
  });
});

 

As well as the tests being faster and more reliable, it has one other major advantage.... this utility function could be easily used by applications built using other UI frameworks such as React or Angular. 

 

I think that this would be a good approach to developing and testing web application components - the tests could be running constantly and incredibly quickly in the background to give you immediate feedback when you make any changes that break a test and the code would be much more easily portable and re-usable between frameworks.

Outcomes