const SuperAwesomeModule = {
featureA: () => {
...
},
featureB: () => {
...
}
}
describe('SuperAwesomeModule', () => {
describe('featureA', () => {
});
describe('featureB', () => {
});
});
describe('SuperAwesomeModule', () => {
describe('featureA', () => {
it('should calculate some super awesome calculation', () => {
expect(SuperAwesomeModule.featureA([1, 2, 4]).toEqual(7);
});
it('should also do this correctly', () => {
expect(SuperAwesomeModule.featureB('...').toBe(true);
});
});
});
expect(foo).toBe(true); // uses JS strict equality
expect(foo).not.toBe(true);
expect(foo).toEqual(482); // uses deep equality
expect(foo).toBeDefined();
expect(foo).not.toBeDefined();
expect(foo).toBeUndefined();
expect(foo).toBeTruthy(); // boolean cast testing
expect(foo).toBeFalsy();
expect(foo).toContain('student'); // find item in array
expect(e).toBeLessThan(pi);
expect(pi).toBeGreaterThan(e);
expect(a).toBeCloseTo(b, 2); // a to be close to b by 2 decimal points
expect(() => {
foo(1, '2')
}).toThrowError();
expect(() => {
foo(1, '2')
}).toThrow(new Error('Invalid parameter type.')
describe('ApiService', function() {
const serviceInTest;
beforeEach(function() {
serviceInTest = new ApiService();
});
afterEach(function() {
...
});
it('retrieves data', function() {
...
});
it('updates data', function() {
...
});
});
describe('SuperAwesomeModule', function() {
beforeEach(function() {
// track all calls to SuperAwesomeModule.asyncHelperFunction()
// and return a mock response
spyOn(SuperAwesomeModule, 'asyncHelperFunction').and.returnValue(Promise.resolve(mockData))
});
describe('featureA', function() {
it('should ...', function() {
expect(SuperAwesomeModule.featureA(x)).toBe(y);
// matchers for spies
expect(SuperAwesomeModule.asyncHelperFunction).toHaveBeenCalled();
});
});
});
it('should do something async', done => {
let value;
setTimeout(() => value = 42, 100);
setTimeout(() => {
expect(value).toBe(42);
done();
}, 200);
expect(value).toBeUndefined();
});
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
We want to configure the testing module
let component: MenuComponent;
let fixture: ComponentFixture< MenuComponent >;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MenuComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render two menu items', () => {
const menuItems = fixture.debugElement.queryAll(By.css('a'));
expect(menuItems.length).toBe(2);
});
Running this, you will get an error:
Can't bind to 'routerLink' since it isn't a known property of 'a'. Since we aren't importing
the module for routing
, Angular doesn't recognize this directive.
import { NO_ERRORS_SCHEMA } from '@angular/core';
schemas: [NO_ERRORS_SCHEMA]
it('should render a different Dashboard link title', () => {
component.dashboard = 'Spies';
fixture.detectChanges();
const dashboardLink = fixture.debugElement.queryAll(By.css('a'))[0];
expect(dashboardLink.nativeElement.textContent).toBe('Spies');
});
TestBed.configureTestingModule({
declarations: [HeroesComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [
{
provide: HeroService,
useValue: mockHeroService,
},
],
}).compileComponents();
const mockHeroService = {
getHeroes: () => of([]),
addHero: () => {},
delete: () => {},
};
let component: HeroesComponent;
let fixture: ComponentFixture< HeroesComponent >;
let heroService: HeroService;
beforeEach(() => {
fixture = TestBed.createComponent(HeroesComponent);
heroService = fixture.debugElement.injector.get(HeroService);
component = fixture.componentInstance;
fixture.detectChanges();
});
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes().subscribe(heroes => (this.heroes = heroes));
}
You should first spy on the service mock and return a response:
spyOn(heroService, 'getHeroes').and.returnValue(of(mockHeroes));
Then, there are two methods of testing this:
it("should assign the heroes' list to the variable (using async) ", async (() => {
//Arreange
spyOn(heroService, 'getHeroes').and.returnValue(of(mockHeroes));
//Act
component.getHeroes();
//Asserts
fixture.whenStable().then(() => {
expect(component.heroes).toBe(mockHeroes);
});
}));
it('...', fakeAsync(() => {
spyOn(heroService, 'getHeroes').and.returnValue(of(mockHeroes));
component.ngOnInit();
flush(); // "flushes" asynchronous tasks
expect(...).toEqual(...);
}));
If you need fine time control, the
tick function simulates the passage of time, and it can take in an optional argument of milliseconds.
it('should be created', inject([HeroService], (service: HeroService) => {
expect(service).toBeTruthy();
}));
beforeEach(inject(
[HeroService, HttpClient, MessageService],
(_heroService: HeroService,
_httpService: HttpClient,
_messageService: MessageService) => {
heroService = _heroService;
httpService = _httpService;
messageService = _messageService;
},
));
describe('#getHeroes',() => {
it('should return heroes from backend getHeroes', () => {
const heroesUrl = 'api/heroes';
const message = 'HeroService: fetched heroes';
spyOn(httpService, 'get').and.returnValue(of(true));
spyOn(messageService, 'add');
heroService.getHeroes().subscribe();
expect(httpService.get).toHaveBeenCalledWith(heroesUrl);
expect(messageService.add).toHaveBeenCalledWith(message);
});
it('should catchError when an error is provoked', () => {
const heroesUrl = 'api/heroes';
const message = 'HeroService: getHeroes failed: undefined';
spyOn(httpService, 'get').and.returnValue(_throw(3));
spyOn(messageService, 'add');
heroService.getHeroes().subscribe();
expect(httpService.get).toHaveBeenCalledWith(heroesUrl);
expect(messageService.add).toHaveBeenCalledWith(message);
});
});
getHeroes(): Observable< Hero[] > {
return this.http.get< Hero[] >(this.heroesUrl).pipe(
tap(heroes => this.log('fetched heroes')),
catchError(this.handleError('getHeroes', [])),
);
}
private log(message: string) {
this.messageService.add(`HeroService: ${message}`);
}
private handleError< T >(operation = 'operation', result?: T){
return (error: any): Observable< T > => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
import { Directive, HostListener, ElementRef } from '@angular/core';
@Directive({
selector: '[myDirective]',
})
export class myDirective {
constructor(private element: ElementRef) {}
@HostListener('keydown', ['$event'])
onKeydown(event) {
const numberRegex = /[0-9]/;
if (numberRegex.test(event.key)) {
event.preventDefault();
}
}
}
< input myDirective type="text" placeholder="Search..." >
@Component({
template: `< input myDirective type="text"/>
< textarea myDirective>`
})
class TestHostComponent {
}
When testing this, we can use the
debugElement and
By to query for the input.
DebugElements have a useful
triggerEventHandler that you can call. In this case, we would trigger the keydown event.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'uppercase'
})
export class UppercasePipe implements PipeTransform {
transform(input: string): any {
return input.toUpperCase();
}
}
import { UppercasePipe } from './uppercase.pipe';
describe('UppercasePipe', () => {
let pipe: UppercasePipe;
beforeEach(() => {
pipe = new UppercasePipe();
});
it('creates an instance', () => {
expect(pipe).toBeTruthy();
});
it('transforms input string to uppercase', () => {
expect(pipe.transform('angular rocks!')).toBe('ANGULAR ROCKS!');
});
});