Techniques and Practices for Testing Angular OPEN331 Duncan Hunter & Adam Stephensen
Adam Stephensen Duncan Hunter Architect / GM at SSW @AdamStephensen Duncan Hunter Architect at SSW @DuncHunter
Who has tested angular 1+?
1979 Testing != debugging 1996 XP 1998 SUnit 2001 JSUnit 2003 TDD 2006 BDD 2006 Selenium 2010 Jasmine 2010 Angular 1.0 2011 Karma 2013 Protractor 2016 Angular 2.0
Agenda Tools Jasmine Faking Dependencies TestBed API TestBed API async and fakeAsync e2e tests
9/12/2018 10:17 PM Tools © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
Angular is designed from the ground up to be tested.
Unit tests e2e test Test specific methods or units of code. Tests an app running in a real browser, interacting with it as a user would.
describe(`Component: JokeComponent`, () => { }); it(`should add 1 + 1 `, () => { expect(1 + 1).toEqual(2); }); it(`should add 1 + 1 `, () => { expect(1 + 1).toEqual(2); });
Angular CLI
Summary - Tools Use Angular CLI Use Karma, Protractor and Jasmine Check out WallabyJS
9/12/2018 10:17 PM Jasmine © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
PH: what we will test as an app image
PH: what we will test as an app image
PH: what we will test as an app image
Jasmine Tests 1 + 1 Component Title Code Demo
describe(`Component: JokeComponent`, () => { }); describe(`Component: JokeComponent`, () => { }); it(`should add 1 + 1 `, () => { expect(1 + 1).toEqual(2); }); it(`should add 1 + 1 `, () => { expect(1 + 1).toEqual(2); }); it(`should have a title of "Chuck Norris Quotes"`, () => { const component = new JokeComponent(null); expect(component.title).toEqual('Chuck Norris Jokes'); }); beforeEach(() => { let component = new JokeComponent(null); };
Summary - Jasmine Default for Angular Community More than syntax it’s a testing framework
Faking Dependencies 9/12/2018 10:17 PM © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
Faking and setup of dependencies is still one of the hardest part of testing
let component = new JokeComponent(fakeJokeService); let fakeJokeService = { getJoke: () => Observable.of(‘FAKE_JOKE’); }; let component = new JokeComponent(fakeJokeService);
let component = new JokeComponent(fakeJokeService); let fakeJokeService = jasmine.createSpyObj('jokeService', ['getJoke']) fakeJokeService.getJoke.and.returnValue(Observable.of(‘FAKE JOKE’); let component = new JokeComponent(fakeJokeService);
Intercept any function call Override the behaviour of the function spyOn(jokeService, ‘getJoke’) .and.returnValue(Observable.of((`FAKE JOKE’)); Spies Let You Intercept any function call Override the behaviour of the function
expect(jokeService.getJoke).toHaveBeenCalled(); expect(jokeService.getJoke).toHaveBeenCalledTimes(2); expect(jokeService.getJokeByType).toHaveBeenCalledWith(‘DadJokes’);
Faking Dependencies Fake Service Jasmine Spy Code Demo
describe(`Component: JokeComponent`, () => { }); let component: JokeComponent; let fakeJokeService: any; beforeEach(() => { fakeJokeService = { getJoke: () => Observable.of(‘FAKE JOKE’) }; component = new JokeComponent(fakeJokeService); it(`should set joke when component is initialised `, () => { component.ngOnInit(); expect(component.joke).toEqual(‘FAKE_JOKE’); });
describe(`Component: JokeComponent`, () => { }); let component: JokeComponent; let fakeJokeService: any; beforeEach(() => { fakeJokeService = jasmine.createSpyObj('jokeService',['getJoke']); fakeJokeService.getJoke.and.returnValue(Observable.of(‘FAKE JOKE’); component = new JokeComponent(fakeJokeService); }; it(`should set joke when component is initialised `, () => { component.ngOnInit(); expect(component.joke).toEqual(‘FAKE_JOKE’); });
Spying Faking Spy functionality and assertions Cleaner code Isolated Duplicated code Forgot to update
Summary - Faking Dependencies Tools you need both fakes and spies Faking and setup of dependencies is still the hardest part of testing The Testbed will you help you ….
9/12/2018 10:17 PM TestBed API © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
Types of Unit Tests Isolated Shallow Integrated HTML template No HTML template HTML template No child components Test the entire app
An NgModule for testing Just the dependencies you need to test Testbed.configureTestingModule({ imports: [HttpModule], declarations: [JokeComponent], providers: [JokeService] }); TestBed API An NgModule for testing Just the dependencies you need to test
Component Fixture fixture = Testbed.createComponent(JokeComponent); component = fixture.componentInstance; debugElement = fixture.debugElement; fixture.detectChanges(); Component Fixture
CSS selector for querying the HTML Template .nativeElement; jokeText = debugElement.query(By.css(`p`)) Query the DOM CSS selector for querying the HTML Template
TestBed API helps you Create a testing module Query HTML templates Detect Changes More…..
TestBed API Configure TestBed Test DOM content Code Demo
describe(`Component: JokeComponent`, () => { }); describe(`Component: JokeComponent`, () => { }); let component: JokeComponent; let jokeService: JokeService; let fixture: ComponentFixture<JokeComponent>; let de: DebugElement; let component: JokeComponent; let jokeService: JokeService; let fixture: ComponentFixture<JokeComponent>; let de: DebugElement; beforeEach(() => { Testbed.configureTestingModule({ imports: [HttpModule], declarations: [JokeComponent], providers: [JokeService], }); fixture = Testbed.createComponent(JokeComponent); component = fixture.componentInstance; jokeService = Testbed.get(JokeService); de = Fixture.debugElement; fixture = Testbed.createComponent(JokeComponent); component = fixture.componentInstance; jokeService = Testbed.get(JokeService); de = Fixture.debugElement;
it(`should get display the joke content`, () => { }); spyOn(jokeService, ‘getJoke’) .and.returnValues( .Observable.of(‘FAKE_JOKE’); fixture.detectChanges(); let joke = de.query(By.css(’p’)).nativeElement; expect(joke.textContent).toEqual('FAKE JOKE');
Summary - TestBed API Big API takes time to learn Makes a NgModule and runs everything in a zone
async and fakeAsync 9/12/2018 10:17 PM © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
Async adds complexity to writing JavaScript unit tests.
Jasmine ‘done’ Callbacks it(`should show quote after getJoke'`, done => { spyOn(jokeService, ’getJoke’) .returnValue('FAKE JOKE 2').then(() => { fixture.detectChanges(); expect(el.textContent).toEqual('FAKE JOKE 2'); done(); }); Jasmine ‘done’ Callbacks Requires chaining promises, handling errors, and calling done Not recommended, but still required for some edge cases
Zones define an execution context for asynchronous operations
async(() => { button.click(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(el.textContent).toEqual('FAKE JOKE 2'); }); async Tests Fixture.whenStable( ) resolves when all pending asynchronous activities within the test complete
fakeAsync(() => { button.click(); tick(3000); fixture.detectChanges(); expect(el.textContent).toEqual('FAKE JOKE'); }); } fakeAsync Tests tick( ) simulates the passage of time until all pending async requests complete, or you can specify a specific amount of time
async and fakeAsync Joke button fakeAsync Joke button async Code Demo
it(`should get next quote on click - with async`, async(() => { })); spyOn(jokeService, ‘getJoke’) .and.returnValues( .Observable.of(‘FAKE_JOKE’), .Observable.of(‘FAKE_JOKE’)); fixture.detectChanges(); let el = de.query(By.css(’p’)).nativeElement; expect(el.textContent).toEqual('FAKE JOKE'); let button = fixuture.debugElement .query(By.css(’button’)).nativeElement; button.click(); fixture.whenStable().then(() => { fixture.detectChanges();; expect(el.textContent).toEqual('FAKE JOKE 2'); });
it(`should get next quote on click`, fakeAsync(() => { })); spyOn(jokeService, ‘getJoke’) .and.returnValues( .Observable.of(‘FAKE_JOKE’), .Observable.of(‘FAKE_JOKE’).timeout(2000)); fixture.detectChanges(); let el = de.query(By.css(’p’)).nativeElement; expect(el.textContent).toEqual('FAKE JOKE'); let button = fixture.debugElement .query(By.css(’button’)).nativeElement; button.click(); tick(3000); fixture.detectChanges(); expect(el.textContent).toEqual('FAKE JOKE 2');
async fakeAsync Simplifies coding of asynchronous tests Tests appear to be synchronous You cannot XHR calls
Async adds complexity to writing JavaScript unit tests.
Summary - async and fakeAsync Async code is edgy to get right New helpers simplify async testing
9/12/2018 10:17 PM e2e tests © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
e2e tests ensure that integrated components function as expected.
e2e Tests Joke title Joke button Code Demo
import { browser, by, element } from ‘protractor’ describe(`Page: Joke Page`, () => { }); describe(`Page: Joke Page`, () => { }); it(`should have a title of "Chuck Norris Jokes"`, () => { browser.get(‘/’); let title = element(by.css(‘h1’).getText(); expect(title).toEqual('Chuck Norris Jokes'); }); it(`should have a title of "Chuck Norris Jokes"`, () => { browser.get(‘/’); let title = element(by.css(‘h1’).getText(); expect(title).toEqual('Chuck Norris Jokes'); }); it(`should have a new joke on button click`, async() => { browser.get(‘/’); let firstJoke = element(by.css(‘p’).getText(); element(by.css(‘button’).click(); let secondJoke = await element(by.css(‘p’).getText(); expect(title).not.toEqual(secondJoke); }); it(`should have a new joke on button click`, async() => { browser.get(‘/’); let firstJoke = element(by.css(‘p’).getText(); element(by.css(‘button’).click(); let secondJoke = await element(by.css(‘p’).getText(); expect(title).not.toEqual(secondJoke); });
Summary - e2e tests Not a unit test it runs in a browser Test user stories versus functions Helps save the manual testers!
Testing is something that the whole team need to practice …. and practice regularly.
www.courses.firebootcamp.com ignite-testing
Summary Tools Jasmine Faking Dependencies TestBed API TestBed API async and fakeAsync e2e tests
Continue your Ignite learning path 9/12/2018 10:17 PM Continue your Ignite learning path Visit Channel 9 to access a wide range of Microsoft training and event recordings https://channel9.msdn.com/ Head to the TechNet Eval Centre to download trials of the latest Microsoft products http://Microsoft.com/en-us/evalcenter/ Visit Microsoft Virtual Academy for free online training visit https://www.microsoftvirtualacademy.com © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.
9/12/2018 10:17 PM Thank you Chat with us in the Speaker Lounge Find us @dunchunter & @adamstephensen © 2014 Microsoft Corporation. All rights reserved. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.