Testing Angular Apps

by Carl Vuorinen / @cvuorinen

Carl Vuorinen

@cvuorinen
cvuorinen
cvuorinen.net

citydevlabs.fi/rekry

Story Time

The moment you start
to need automated tests,
it’s already too late

Testing Angular

Angular CLI FTW!


ng new my-new-app
        

ng generate component my-new-component
        

Testing Angular

  • Unit & integration testing:
    • Test runner: Karma
    • Testing framework: Jasmine
    • Helpers: Angular testing utilities

  • End-to-End testing:
    • Protractor

Running Karma


ng test
        

Running Protractor


ng e2e
        

describe('Jasmine test suite', () => {
  beforeEach(() => {
    // instanciate Subject Under Test
    // setup dependencies
  })

  it('should test expectation', () => {
    // execute code that should be tested
    const result = 1 + 1

    // and assert expectation
    expect(result).toEqual(2)
  })
})
        
describe('My App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display welcome message', () => {
    page.navigateTo();

    expect(page.getParagraphText()).toEqual('Welcome to my app!');
  });
});
describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ MyComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Chrome 64.0.3282 (Linux 0.0.0): Executed 1 of 1 (1 FAILED)
Chrome 64.0.3282 (Linux 0.0.0) MyComponent should create FAILED
  Error: StaticInjectorError(DynamicTestModule):
    StaticInjectorError(Platform: core)[MyComponent -> MyService]:
      NullInjectorError: No provider for MyService!
  'another-component' is not a known element:
    1. If 'another-component' is an Angular component,
       then verify that it is part of this module.
  ...
        

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ MyComponent, AnotherComponent, ... ],
      imports: [ ReactiveFormsModule ],
      providers: [ MyService ],
    })
    .compileComponents();
  }));

  ...
});
        

import {AppModule} from './app.module';

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ AppModule ],
    })
    .compileComponents();
  }));

  ...
});
        

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ MyComponent ],
      schemas: [ NO_ERRORS_SCHEMA ],
      providers: [ MyService ],
    })
    .compileComponents();
  }));

  ...
});
        
Shallow test

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async(() => {
    const serviceMock = jasmine.createSpyObj('MyService', ['someAction']);

    TestBed.configureTestingModule({
      declarations: [ MyComponent ],
      schemas: [ NO_ERRORS_SCHEMA ],
      providers: [ {provide: MyService, useValue: serviceMock } ]
    })
    .compileComponents();
  }));

  ...
});
        
Isolate test by providing mock service

describe('MyComponent', () => {
  let component: MyComponent;
  let service: MyService;

  beforeEach(() => {
    service = jasmine.createSpyObj('MyService', ['someAction']);

    component = new MyComponent(service);
    component.ngOnInit();
  });

  ...
});
        
Component is just
regular JS class
* need to call lifecycle hooks manually

Types of Tests

Unit Integration E2E
Fast Yes ~ No
Reliable Usually ~ Sometimes
Isolate Yes ~ No
Simulates real use No ~ Yes

Why do you write tests?

  1. Verify correctness
  2. Prevent regressions
  3. Support refactoring
  4. Design aid
  5. Documentation

Don’t get stuck on testing dogma


  • Dogma is inflexible.

  • Dogma kills creativity.
Testing needs flexibility
Testing needs creativity

An imperfect test today
is better than a perfect test someday


  • Perfect is the enemy of good

  • Write the best test you can today

An ugly test is better than no test


  • When the code is ugly, the test may be ugly

  • You don’t like to write ugly tests, but
    ugly code needs testing the most
The problem with quick and dirty...
is that dirty remains long after quick has been forgotten. Steve C McConnell

THE END


Questions?


@cvuorinen

Links