Testing Routines and Angular Views

About Me

  • Name: Tomas Miliauskas

  • Company: Wix

  • Main Focus: Client Side Engineering

  • Email/Hangouts: tomasm@wix.com

Testing Routines and Angular Views

One Important Note

MANUAL

TESTING

Testing === Automated Testing

1. Testing

What is Software Testing?

Software testing is a method of assessing the functionality of a software program.

http://whatis.techtarget.com  

What is the proper way to test your software?

Perform testing in an environment that is close to what user actually gets.

Just probably it would take ages to test everything...

So we break testing into levels

e2e testing

integration testing

unit testing

2. Testing in AngularJS

Protractor

was designed to test e2e/integration

Karma + Jasmine/Mocha

  • Modules

  • Controllers

  • Filters

  • Services

  • Directives

are to unit test the main routines

3. Issues

Some real world example

Option 1/3

Option 2/3

Option 3/3

Wanna guess the winner?

How would we test it in regular way?

function openApp(option) {
  browser.addMockModule('mockModule', function (opt) {
    angular.module('mockModule', []).run(function (experiments, hotelState) {
      experiments.UpgradeSection = opt;
      experiments.HideGreedyPopup = true;
      hotelState.upgraded = false;
    });
  }, option);
  browser.get('http://localhost:9000/back-office-v2.html');
}

1. Define the mocks

describe('A/B test for upgrade button', function () {
  var upgrade;

  function openApp(option) { ... }

  it('should display upgrade button at the bottom', function () {
    openApp('bottom');
    upgrade = $('#upgrade-bar');
    expect(upgrade.isDisplayed()).toBe(true);
  });

  it('should display upgrade button on the top-right corner', function () {
    openApp('top-right');
    upgrade = $('.upgrade-section');
    expect(upgrade.isDisplayed()).toBe(true);
  });

  it('should display upgrade button inside the menu', function () {
    openApp('menu');
    upgrade = $('.upgrade-section-2');
    expect(upgrade.isDisplayed()).toBe(true);
  });
});

2. Test each case

Finished in 6.052 s

Overall it takes: 14.950 s

3. Run tests using Protractor...

Taking notes

  • Full application needs to be loaded for each test
  • Server has to be up and running
  • All the external/internal dependencies will be loaded
  • Styles and images will be loaded
  • Proper setup has to be initiated.
  • Functionality is not important here to test
  • Debugging can be time-consuming

4. The Idea

Let's get back to the different levels of testing

e2e testing

integration testing

unit testing

Test the

While doing TDD, we test almost everything

around 5000 tests in total

Let's try to unit test

views & templates?

5. Walk-through

the solution

1. Setup karma.conf.js

  • load views and templates

  • npm install karma-ng-html2js-preprocessor

  • add ng-html2js to preprocessors list

  • specify additional options for ng-html2js

1. Setup karma.conf.js

module.exports = function (config) {
  config.set({
    plugins: [
      'karma-jasmine',
      'karma-phantomjs-launcher',
      'karma-ng-html2js-preprocessor'
    ],

    ngHtml2JsPreprocessor: { ... },

    frameworks: ['jasmine'],

    files: [
      ...
      'app/template/**/*.html',
      'app/views/**/*.html'
    ]
    
    ...
  });
};

2. Mock/stub your modules, services & filters

beforeEach(function () {
  module('backOfficeApp', function ($provide, $filterProvider) {
    $provide.constant('hotelState', { ... });
    $provide.constant('$wix', {
      Dashboard: { ... },
      Billing: { ... },
      Extensions: { ... }
    });
    $filterProvider.register('translate', function () {
      return function (str) {
        return str;
      };
    });
  });
});

Note: mocking/stubbing is optional

3. Create a scope object

var $rootScope;

beforeEach(function () {
  inject(function ($injector) {
    $rootScope = $injector.get('$rootScope');
  });

  $scope = $rootScope.$new();
  $scope.vm = {}; // for controller as syntax
  $scope.vm.experiments = {};
  $scope.vm.premiumManager = {
    showBoUpgradeBar: jasmine.createSpy().andReturn(true),
    upgradeFromBo: jasmine.createSpy(),
    ready: jasmine.createSpy()
  };
});

4. Use $templateCache service to retrieve your view/template

var $templateCache, template;

beforeEach(function () {
  inject(function ($injector) {
    $templateCache = $injector.get('$templateCache');
  });

  template = $templateCache.get('views/layout.html');
});

Note: template id === path to a template

5. Compile your view/template

  • save your element into a variable

  • Inject Angular $compile service

  • pass template/view and scope object

  • call $scope.$digest()

  • put your template/view inside a <div> element

5. Compile your view/template

var $templateCache, $rootScope, $compile, $scope, template;

beforeEach(function () {
  module('backOfficeApp', function () { ... });
    
  inject(function ($injector) {
      $templateCache = $injector.get('$templateCache');
      $rootScope = $injector.get('$rootScope');
      $compile = $injector.get('$compile');
  });

  template = ...;
  $scope = ...
});

function compile() {
  var elem = $compile(angular.element('<div></div>').html(template))($scope);
  $scope.$digest();
  return elem;
}

6. Write unit tests

describe('A/B test for upgrade button', function () {
  var $templateCache, $rootScope, $compile, $scope, template;

  beforeEach(function () { ... });

  function compile() { ... }

  it('should display upgrade button at the bottom', function () {
    $scope.experiments.UpgradeSection = 'bottom';
    expect(compile().find('#upgrade-bar').length).toBe(1);
  });

  it('should display upgrade button on the top-right corner', function () {
    $scope.experiments.UpgradeSection = 'top-right';
    expect(compile().find('.upgrade-section').length).toBe(1);
  });

  it('should display upgrade button inside the menu', function () {
    $scope.experiments.UpgradeSection = 'menu';
    expect(compile().find('.upgrade-section-2').length).toBe(1);
  });
});

Finished in 0.063 s

Overall it takes: 7.180 s

So, let's run our unit tests...

...and it's even faster when watch task is running in the background

So what happens behind the scenes?

Let's Wrap Things Up

Compare the time difference

Time in total: 14,950 s vs. 7,180 s

Finished in: 6,052 s vs. 0,063 s

Pros

  • does not contradict testing philosophy

  • shorter tests runtime

  • simple setup and debugging

  • easier to mock/stub things

  • gives you more control in tests

  • helps you to reduce complexity of your tests

  • better tests performance

Cons

  • risk of mixing responsibilities of your tests

  • no test coverage

  • elements are not appended to actual DOM

  • losing integration between components

  • might take time to master

For the record...

Thanks for attending

Testing Routines and Angular Views

By Tomas Miliauskas

Testing Routines and Angular Views

  • 1,571