Unit Testing

Karma

  • JS Tests need a Runtime
  • Our App runs in the Browser
  • So our Tests should also run in the Browser

Mocha

  • Define Unit Tests and Test Suites
  • Behaviour Driven Design
  • Use describe() for a suite and it() for a unit

Chai

  • Mocha has no matchers
  • Multiple matcher styles:
    should, expect and assert
  • Check out the docs
                    
                        describe('PizzaService', () => {
                            describe('getPizza', () => {
                                it('should return Salami Pizza when no param given', () => {
                                    const pizzaService = new PizzaService();
                                    const salamiPizza = new Pizza('Salami');

                                    const actual = service.getPizza();

                                    expect(actual).to.eql(salamiPizza);
                                });
                            });
                        });
                    
                

Mocha/Chai vs JUnit

  • @Test => it('should ...')
  • @Ignore => describe.skip, it.skip
  • Excecute one test => describe.only, it.only
  • Assert => expect
  • @Before => beforeEach
  • @After => afterEach

Async Testing

                    
                        describe('PizzaService', () => {
                            describe('getAsyncPizza', () => {
                                it('should return Salami Pizza asynchronously', () => {
                                    const pizzaService = new PizzaService();
                                    const salamiPizza = new Pizza('Salami');

                                    const promise = service.getAsyncPizza();

                                    promise.then(actual => {
                                        expect(actual).to.eql(salamiPizza);
                                    });
                                });
                            });
                        });
                    
                

Problem

  • Test is green
  • The async Exception is not catched by our test
  • When Exception happens, the test is already finished

Solution

  • $digest loop
  • Angular Promises are bound to it
  • Tests can be written almost like synchronous
                    
                        describe('PizzaService', () => {
                            describe('getAsyncPizza', () => {
                                it('should return Salami Pizza asynchronously', () => {
                                    let actual;
                                    const pizzaService = new PizzaService();
                                    const salamiPizza = new Pizza('Salami');

                                    service.getAsyncPizza()
                                        .then(pizza => actual = pizza);
                                    $rootScope.$digest;

                                    expect(actual).to.eql(salamiPizza);
                                });
                            });
                        });
                    
                

Without Angular?

                    
                        describe('PizzaService', () => {
                            describe('getAsyncPizza', () => {
                                it('should return Salami Pizza asynchronously', (done) => {
                                    const pizzaService = new PizzaService();
                                    const salamiPizza = new Pizza('Salami');

                                    const promise = service.getAsyncPizza();

                                    promise.then((actual) => {
                                        expect(actual).to.eql(salamiPizza);
                                        done();
                                    }).catch(done);
                                });
                            });
                        });
                    
                

Angular Injection

Test Setup

                    
                        describe('pizza.service.module', () => {
                            beforeEach(angular.mock.module('pizza.service.module'));

                            let PizzaService;
                            let SomeService;

                            beforeEach(angular.mock.inject((
                                _PizzaService_,
                                _SomeService_
                            ) => {
                                PizzaService = _PizzaService_;
                                SomeService = _SomeService_;
                            });
                            ...
                        });
                    
                

Spy with Sinon

                    
                        describe('calculate', () => {
                            it('should call SomeService.add two times', () => {
                                SomeService.add = sinon.spy();

                                PizzaService.calculate();

                                sinon.assert.calledTwice(SomeService.add);
                            });
                        });
                    
                

Anything non-Angular is injected by Webpack which we will cover later!

Excercise Time!

Angular Directive Testing

Test Setup

                    
                        describe('pizza.service.module', () => {
                            beforeEach(angular.mock.module('pizza.service.module'));

                            let $compile;
                            let $rootScope;

                            beforeEach(angular.mock.inject((
                                _$compile_,
                                _$rootScope_
                            ) => {
                                $compile = _$compile_;
                                $rootScope = $rootScope;
                            }));
                            ...
                        });
                    
                

Compile the Directive

                    
                        it('should store name of Pizza in Scope', () => {
                            const $scope = $rootScope.$new();
                            const element = $compile(
                                ``
                            )($scope);

                            $rootScope.$digest();

                            expect(element.isolateScope().myValue).to.equal('Pizza Salami');
                        });
                    
                

Test HTML Logic

                    
                        it('should show Pizza Title', () => {
                            const element = $compile(
                                ``
                            )($rootScope.$new());

                            $rootScope.$digest();

                            expect(element.find('h3').text()).to.equal('Pizza Salami');
                        });
                    
                

Testing Interaction

                    
                        it('should hide Pizza Title on click', () => {
                            const element = $compile(
                                ``
                            )($rootScope.$new());

                            $rootScope.$digest();
                            element.find('h3').click();
                            $rootScope.$digest();

                            expect(element.find('h3')).to.be.undefined;
                        });
                    
                

Test Helper

We have a TestHelper which does this compiling for us

compileDirective(scope, html)

                    
                        const result = TestHelper.compileDirective(
                            $rootScope.$new(),
                            ``
                        );

                        /*{
                            element,
                            compiledElement,
                            directiveScope,
                        }*/
                    
                

Earlier Example

                    
                        it('should hide Pizza Title on click', () => {
                            const actual = TestHelper.compileDirective(
                                $rootScope.$new(),
                                ``
                            );

                            $rootScope.$digest();
                            actual.compiledElement.find('h3').click();
                            $rootScope.$digest();

                            expect(actual.compiledElement.find('h3')).to.be.undefined;
                        });
                    
                

Excercise Time!

Question Time!