Testing

AngularJS






Samuli Ulmanen
Web Developer, Rovio Entertainment Ltd.
@sulmanen




Disclaimer

... or why AngularJS isn't more talked of in Helsinki

'We have hit peak javascript'

'Start thinking for yourselves, ditch this s**t before it's too late and learn how to write some actual f***ing code'

'If you need handholding with factories and providers and service provider factories ...'

You have ruined javascript
Ron Ashton

Why ?

test writing is like this



when I don't write tests




Are we making the right tradeoff?



manually checking that stuff works in staging, 
freeze and deploy
vs.
 complete test suite 
vs.
partial test suite and manual verification

Motivation





ship stuff that works
minimize failure demand

but...
can I stay out of the debugger and save time as well?

Got test suite?

 

refactor with confidence

documentation that doesn't go out of date

BDD only
communicate to stakeholders what's done

test driving



'meh'

'go away'

'why are u pushing this stuff'

I wanna do it like in the movies




Test drive 



focus in the right place

stay out of the debugger

code guaranteed testable

analysis and code ready for change 



Test drive




stay out of the debugger

up front costs 'cause you have to learn

Acceptance test drive





ATDD is another tool in the box to facilitate goal oriented communication and get everybody on the same page. It is about defining in a non-ambiguous way what is desired.

Stephan Schwab


methods

... have history


IoC & Dependency Injection
aid in writing testable code

Test Doubles
isolate code under test


Methods...



TDD evolved to BDD
red, green, refactor
communicate

ATDD
implement the right thing

IOC

 public static void main() {   Car car = new Car('red');   car.drive(200);   car.fillUp();   car.turnRight();   car.turnLeft();}
class Simulator {   private Car car;   Container(Car car) {     this.car = car;   }   void run() {     car.drive(200);     car.fillUp();     ...

IoC

1983
Hollywood principle
The Mesa Programming Environment
Richard E. Sweet


1988
IoC
Martin Fowler

DependeNcy injection 



IoC 
implementation 
with contextual lookup 

'Injection is the passing of a dependency to a dependent'

... enable the use of Test Doubles

Test doubles



Spy
Stub which records on how it was called


Mock
specification of expected calls


... enable us to write concise non brittle tests

test doubles


Dummy
pass around but never used

Fake
actual replacement component

Stub
returns specific values for  a test

more: mocks aren't stubs Martin Fowler

TDD


1960
'practice of test-first development, planning and writing tests before each micro-increment'

'To shorten the total development time, some formal test documents (such as for acceptance testing) have been developed in parallel (or shortly before) the software is ready for testing. '

NASA Project Mercury 
to put a man in space

TDD: So gagarin made it first?




April 12 1961
test driving failed to ship?

TDD


1979
the art of software testing
Separate debugging from testing
Glenford J. Meyers


TDD




2003
TDD rediscovered
Kent Beck XP fame

red, green refactor iterations
keep programmer focus
save time keeping out of debugger and avoid waste

BDD


2006
BDD, an evolution from TDD
Dan North

'I kept coming across the same confusion and misunderstandings. Programmers wanted to know where to start, what to test and what not to test,
        how much to test in one go, what to call their tests, and how to understand why a test fails' (http://dannorth.net/introducing-bdd/)

 ATDD

1960
NASA project Mercury

1996
realistic examples as a single source of truth
WyCash+ Project
Ward Cunningham

2002
Specification by Example
Martin Fowler

Materials


Task runner


grunt

gulp

make

something else perhaps...

Test Framework


Jasmine 

Mocha

Chai

Qunit

sinon.js

Test Runner




Karma

intern

Object mocking



jsmockito

jsmock

sinon.js has mocks

Protractor


angular-mocks.js

window.module

  • initialise module
  • mock objects
  • include ngMock

window.inject

  • initialise $injector

window.dump

  • stringify

$httpBackend


expect order and existence of requests

expectGET


respond to any request

whenGET

Fixtures




the hard part 
or 
hurdle

Gherkin




Given
When
Then

Reading Gherkin in jasmine


 describe('function load', function() {
    it('exists', function() {
       expect(rvoShortyCtrl.load).toBeDefined(); // Given
    });

    describe('gets list url list', function() {
       beforeEach(inject(function(rvoShortyAllUrl, rvoUrlListDummy) {
     $httpBackend.expectGET(rvoShortyAllUrl).respond(200, rvoUrlListDummy);
             rvoShortyCtrl.load(); // When
             $httpBackend.flush();
           }));

       it('and has url list populated', function() {
         expect(rvoShortyCtrl.urls.length).toBeGreaterThan(0); // Then
       });
});


Controllers


 beforeEach(module('schedulerUi'));

        beforeEach(inject(function ($rootScope, $controller, _rvoJobsServiceStub_, _rvoAppsServiceStub_, rvoAddStepTypeMsg, _rvoSchedulesServiceStub_) {
            scope = $rootScope.$new();
            ctrl = $controller('rvoJobCtrl', {
                $scope: scope,
                RVO_SCHEDULER_JOB_RUNNING: 'RUNNING',
                rvoJobsService: _rvoJobsServiceStub_,
                rvoAppsService: _rvoAppsServiceStub_,
                rvoSchedulesService: _rvoSchedulesServiceStub_
            });
            jobCtrl = scope.rvoJobCtrl;
            serviceMock = _rvoJobsServiceStub_;
            appsServiceMock = _rvoAppsServiceStub_;
            schedulesServiceMock = _rvoSchedulesServiceStub_;
            stepType = rvoAddStepTypeMsg;
        }));

Filter fixture


 describe('rvoNumberFormat', function() {
        var filter;

        beforeEach(module('rvoFilter'));

        beforeEach(inject(function($filter) {
            filter = $filter('rvoNumberFormat');
        }));

        it('should exist', function() {
            expect(filter).toBeDefined();
        });

        it('should withstand undefined', function() {
            expect(filter()).toBe('');
        });

        it('should format according to numeral.js', function() {
            expect(filter(0.23001, '.00')).toBe('.23');
        });

Services

Injecting test doubles was tricky

 describe('JobsService', function () {
        var jobsService, $httpBackend, timeout, mockJobs;

        beforeEach(module('schedulerUi', function($provide) {
            $provide.value('rvoSchedulesService', {
                getAppSchedules: function () {
                },
                getAllApps: function() {
                },
                reload: function() {

                }
            });
        }));

directives


 describe('rvoSchedulerClusterConfig', function () {
        var el, scope, parentScope, ctrl;

        beforeEach(module('schedulerUi'));

        beforeEach(inject(function ($rootScope, $compile) {
            parentScope = $rootScope.$new();
            parentScope.base = {
                cluster: {}
            };
            parentScope.nodeTypes = ['one', 'two'];
            parentScope.nodeCounts = [1, 2];
            el = angular.element('<rvo-scheduler-cluster-config cluster="base.cluster" node-types="nodeTypes" node-counts="nodeCounts"></rvo-sheduler-cluster-config>');
            $compile(el)(parentScope);
            parentScope.$digest();
            scope = el.isolateScope();
            ctrl = scope.rvoSchedulerClusterConfig;
        }));

Directives



Use a $templateCache preloader such as 
grunt-angular-prangler 

include templates.js 
with test dependencies

to prevent xhr's to directive templates



Providers

avoid at all costs
but
if you have to
        var stateProvider;

        beforeEach(function () {
            var fake = angular.module('module.test', []);
            fake.config(function (module_stateProvider) {
                stateProvider = module_stateProvider;
            });

            module('module', 'module.test');
            inject(function () {});
        });

        it('exists', function () {
            expect(stateProvider).toBeDefined();
        });

















Providers


include the provider
before 
the config block

use one when
 you need to test code inside config block
or 
you need a service which needs config
before its wired to other components

Gotchas


there were many...

but

Phantom PhantomJS 
instances were the strangest 


Page objects





Abstraction between the DOM and your tests.


PO benefits




Tests are easier to read

Suite is less brittle 
var ShortyHome = function() {
    var urlInput = element(by.model('rvoShortyCtrl.url'));
    var button = element(by.css('.btn.btn-success'));
    var byError = by.css('.rvo-shorty-error');

    var byShortLink = by.binding('rvoShortyCtrl.shortUrl');
    var title = element(by.css('h1'));

    this.enterUrl = function(url) {
        urlInput.sendKeys(url);
    };

    this.shorten = function() {
        return button.click();
    };

    this.error = function() {
        return element(byError).getText();
    };

    this.linkText = function() {
        return element(byShortLink).getText();
    };

    this.gotoLink = function() {
        element(byShortLink).click();
    };

    this.title = function() {
        return title.getText();
    };
};

module.exports = ShortyHome;

Example test


Made with Slides.com