Writing Jasmine Templates
Tags: javascript, jasmine, modules, templates
Published: 2019-05-22 18:11:52

This started as an exercise to solve a huge refactor issue I have and turned into an even larger architectural refactoring. The problem, some e2e and functional tests have grown out of control and being based on the user's POV the assertions are repetitive with subtle variations. The immediate solution is obvious, update all the tests and move on. However, I like being a lazy engineer (thanks to [Jem Young] (https://twitter.com/JemYoung) ) and really don't want to go through this process again.

The solution I came up with is abstracting the tests to a module. Note: the SupportFunctions module is just a group of a methods that handle login and so forth.

Starting spec files:
``` javascript
/* base spec file 1 */
'use strict';
const SF = require( '../path/SupportFunctions' );

describe( 'landing page', () => {
  beforeAll( () => {
    SF.login( validUser );
  } );

  describe( 'page header', () => {
    it( 'displays the header', () => {
      expect( element( by.css( '.header' ) ).isDisplayed() ).toBe( true );
    } );

    it( 'displays the menu bar', () => {
      expect( element( by.css( '.menu-bar' ) ).isDisplayed() ).toBe( true );
    } );

    it( 'hides the error page', () => {
      expect( element( by.css( '.error-page' ) ).isDisplayed() ).not.toBe( true );
    } );

    /** remaining test here **/
  } );
} );
```
``` javascript
/* base spec file 2 */
'use strict';
const SF = require( '../path/SupportFunctions' );

describe( 'landing page', () => {
  beforeAll( () => {
    SF.login( invalidUser );
  } );

  describe( 'page header', () => {
    it( 'displays the header', () => {
      expect( element( by.css( '.header' ) ).isDisplayed() ).not.toBe( true );
    } );

    it( 'displays the menu bar', () => {
      expect( element( by.css( '.menu-bar' ) ).isDisplayed() ).not.toBe( true );
    } );

    it( 'displays the error page', () => {
      expect( element( by.css( '.error-page' ) ).isDisplayed() ).toBe( true );
    } );

    /** remaining test here **/
  } );
} );
```
As you can see the workflows are the same, but the assertions have different expectations. Here are the two ways I will incorporate modules to simplify maintenance. The first is to abstract the it() methods.
``` javascript
/* it() abstraction module */
'use strict';
let ItModularJasmine = ( () => {
  function ItModularJasmine() {}

  ItModularJasmine.prototype = {
    headerAssertion: function( isTrue ) {
      return it( 'displays the header', () => {
        expect( element( by.css( '.header' ) ).isDisplayed() ).toBe( isTrue );
      } );
    },

    menuBarAssertion: function( isTrue ) {
      return it( 'displays the menu bar', () => {
        expect( element( by.css( '.menu-bar' ) ).isDisplayed() ).toBe( isTrue );
      } );
    },

    errorPageAssertion: function( isTrue ) {
      return it( 'displays the error page', () => {
        expect( element( by.css( '.error-page' ) ).isDisplayed() ).toBe( isTrue );
      } );
    }
  }

  return ItModularJasmine;
} )();

module.exports = new ItModularJasmine();
```
Now with our test abstraction module in place our specs files become a lot clearer and easier to maintain.
``` javascript
/* it() modular file 1 */
'use strict';
const MJ = require( '../path/ItModuleJasmine.module' ),
      SF = require( '../path/SupportFunctions' );

describe( 'landing page', () => {
  beforeAll( () => {
    SF.login( validUser );
  } );

  describe( 'page header', () => {
    MJ.headerAssertion( true );

    MJ.menuBarAssertion( true );

    MJ.errorPageAssertion( false );
  } );
} );
```
``` javascript
/* it() modular file 2 */
'use strict';
const MJ = require( '../path/ItModuleJasmine.module' ),
      SF = require( '../path/SupportFunctions' );

describe( 'landing page', () => {
  beforeAll( () => {
    SF.login( invalidUser );
  } );

  describe( 'page header', () => {
    MJ.headerAssertion( false );

    MJ.menuBarAssertion( false );

    MJ.errorPageAssertion( true );
  } );
} );
```
It() blocks are not the only thing that can be abstracted to a module. A whole describe() block can be abstracted as well. Which looks like this:
``` javascript
/* describe() module abstraction */
'use strict';
let DescribeModule = ( () => {
  function DescribeModule {}

  DescribeModule.prototype = {
    pageHeaderAssertions: function( isHeader, isMenuBar, isErrorPage ) {
      return describe( 'page header', () => {
        it( 'displays the header', () => {
          expect( element( by.css( '.header' ) ).isDisplayed() ).toBe( isHeader );
        } );

        it( 'displays the menu bar', () => {
          expect( element( by.css( '.menu-bar' ) ).isDisplayed() ).toBe( isMenuBar );
        } );

        it( 'displays the error page', () => {
          expect( element( by.css( '.error-page' ) ).isDisplayed() ).toBe( isErrorPage );
        } );
      } );
    }
  }

  return DescribeModule;
} )();

module.exports = new DescribeModule();
```
Now the spec files are even clearer and shorter.
``` javascript
/* describe modular file 1 */
'use strict';
const MJ = require( '../path/DescribeModule' ),
      SF = require( '../path/SupportFunctions' );

describe( 'landing page', () => {
  beforeAll( () => {
    SF.login( validUser );
  } );

  MJ.pageHeaderAssertions( true, true, false );
} );
```
``` javascript
/* describe module file 2*/
'use strict';
const MJ = require( '../path/DescribeModule' ),
      SF = require( '../path/SupportFunctions' );

describe( 'landing page', () => {
  beforeAll( () => {
    SF.login( validUser );
  } );

  MJ.pageHeaderAssertions( false, false, true );
} );
```
As with anything in this modular pattern you can mix and match. The follow is the final refactoring of the describe module from above.
```javascript
/* structure from before */
--- 
MixedModule.prototype = {
  pageHeaderAssertions: function( isHeader, isMenuBar, isErrorPage ) {
    return describe( 'page header', () => {
      this.headerAssertion( isHeader );
      this.menuBarAssertion( isMenuBar );
      this.errorPageAssertion( isErrorPage );
    } );
  },

  headerAssertion: function( isTrue ) {
    return it( 'displays the header', () => {
      expect( element( by.css( '.header' ) ).isDisplayed() ).toBe( isTrue );
    } );
  },

  menuBarAssertion: function( isTrue ) {
    return it( 'displays the menu bar', () => {
      expect( element( by.css( '.menu-bar' ) ).isDisplayed() ).toBe( isTrue );
    } );
  },

  errorPageAssertion: function( isTrue ) {
    return it( 'displays the error page', () => {
      expect( element( by.css( '.error-page' ) ).isDisplayed() ).toBe( isTrue );
    } );
  }
}
---
```
##Update
Now that I've been working through reorg of my code here are some things I've done to make life easier and some issues with this type of structure.

And a couple templates it became clear that I needed a container file for all the require statements, other the top of the spec file would look like this:
``` javascript
'use strict';
const template1 = require( '../path/template1' ),
      template2 = require( '../path/template2' );
/* and so forth */
```
The container is straight forward to setup;
``` javascript
module.exports = function() {
  return {
    template1: require( '../path/template1' ),
    template2: require( '../path/template2' )
  /* and so forth */
  };
};
```
In the spec the usage looks like this:
``` javascript
'use strict';
const templates = require( '../path/container' );

templates().template1.method();
templates().template2.method();
```

###Early Pitfall
In testing this concept out and trying various implementation mechanics I used console.log() for debugging *(don't roll your eyes you do it too)* and I had a couple of variables like this:
``` javascript
let input = input || 0;
```
Because of closure the variable wasn't a reliable value inside the methods that used it.