Angular Testing
Contents of this page:
- Useful links
- Different ways of accessing html elements
- Different ways of spying on dependencies
- Different ways of creating test fixtures
- Imports, Declarations, Providers and Component Properties
Useful links
- Good testing practices:
- https://timdeschryver.dev/blog/good-testing-practices-with-angular-testing-library
- Testing the router:
- https://medium.com/burak-tasci/using-jasmine-framework-to-test-angular-router-b568a232efed
- https://stackoverflow.com/questions/53823091/unable-to-test-routing-using-routertestingmodule
- https://stackoverflow.com/questions/40720514/angular-2-unit-test-cannot-read-property-root-of-undefined
- chapter about testing routes
- General testing stuff:
- https://stackoverflow.com/questions/42777612/detecting-changes-to-service-stub-using-testbed
- https://semaphoreci.com/community/tutorials/testing-services-in-angular-2
- https://stackoverflow.com/questions/45839955/angular-unit-test-for-a-subscribe-function-in-a-component
- Testing library cheat sheet
- Testing Dependencies:
- https://indepth.dev/testing-and-faking-angular-dependencies/
- Difference between declarations, providers, and import in NgModule
- More on declarations, providers, and import
Different ways of accessing html elements
- Cheatsheet here for methods like
getByText
- Some examples of clicking, typing and selecting
- Some examples of testing input validation here
// getByText throws an error if it can't find what it's looking for,
// so you don't need an expect statement.
// If we don't want Angular Testing Library to throw an error we can
// use the queryBy and queryAllBy queries instead (see cheatsheet above, and example below).
const component = await render(...);
component.getByText('Cancel');
//
// click a button
const component = await render(...);
const cancel = component.getByText('Cancel');
cancel.click();
//
// Use queryBy instead of getBy if you want to test for absence.
expect(component.queryByText('Cancel')).toBeNull();
//
// modal dialog:
const dialog = await within(document.body).findByRole('dialog');
const cancel = within(dialog).getByText('Cancel');
cancel.click();
//
// test Ids and inner html
const component = await render(...);
const link = component.getByTestId("add-thingy-info");
expect(link.innerHTML).toContain("Add thingy information");
//
// click a button
const component = await render(...);
component.click(component.getByText('Cancel'));
//
// type text into an input:
const component = await render(...);
const nmdsIdInput = component.container.querySelector('input[name=nmdsid]') as HTMLElement;
nmdsIdInput.nodeValue = '';
component.type(nmdsIdInput, 'new text');
//
// Clicking, typing and selecting
// There's a lot of info on this in the article linked to above
//
// not sure this ever worked:
await within(document.body).getByText('Save and continue').click();
//
// checkbox (I'm not sure I ever got this working though)
const component = await render(...);
let options: DebugElement[] = component.fixture.debugElement.queryAll(By.css('input[type="radio"]'));
options[1].triggerEventHandler('change', { target: options[1].nativeElement });
//
// also checkbox (I'm not sure I ever got this working either though)
const component = await render(...);
import { By } from '@angular/platform-browser';
let checkbox1 = component.fixture.debugElement.query(By.css("input")).nativeElement;
await checkbox1.click();
Different ways of spying on dependencies
- Take dependency direct from component
const component = await render(MyComponent, { imports: [...] }); const spy = spyOn(component.fixture.componentInstance.establishmentService, 'doThing').and.callThrough(); ... expect(spy).toHaveBeenCalled();
- Use TestBed as injector
First way:
import { getTestBed } from '@angular/core/testing';
const component = await render(MyComponent, {
imports: [...],
providers: [
{
provide: EstablishmentService,
useClass: MockEstablishmentService
}]
});
const injector = getTestBed();
const establishmentService = injector.get(EstablishmentService) as EstablishmentService;
const spy = spyOn(establishmentService, 'doThing').and.returnValue(
of(fakeResponse);
...
expect(spy).toHaveBeenCalled();
Second way:
import { TestBed } from '@angular/core/testing';
const component = await render(MyComponent, {
imports: [...],
providers: [
{
provide: EstablishmentService,
useClass: MockEstablishmentService
}]
});
const establishmentService = TestBed.get(EstablishmentService);
const spy = spyOn(establishmentService, 'doThing').and.returnValue(
of(fakeResponse);
...
expect(spy).toHaveBeenCalled();
Different ways of creating test fixtures
- Use
TestBed.configureTestingModule
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([{ path: '', component: AppComponent }])],
declarations: [AppComponent],
}).compileComponents();
const fixture = TestBed.createComponent(AppComponent);
- Use
render
const { fixture } = await render(ParentRequestsComponent, { imports: [ ReactiveFormsModule, HttpClientTestingModule, SharedModule, RouterTestingModule], declarations: [ParentRequestComponent], providers: [ { provide: WindowRef, useClass: WindowRef }], componentProperties: { parentRequests }, });
Imports, Declarations, Providers and Component Properties
const { fixture } = await render(ParentRequestsComponent, {
imports: [
ReactiveFormsModule,
HttpClientTestingModule,
SharedModule,
RouterTestingModule],
declarations: [ParentRequestComponent],
providers: [
{
provide: EstablishmentService,
useClass: MockEstablishmentService
}],
componentProperties: {
parentRequests
},
});
- Difference between declarations, providers, and import in NgModule
- More on declarations, providers, and import
Imports
Allow your test component to access any imports it needs.
Declarations
Providers
Inject mocks and spy on them.
Component Properties
Pass in data / objects needed as @Input properties on your component. Mock outputs, eg submit buttons:
it('form should display error messages and submit if valid', async () => {
const submitSpy = jasmine.createSpy('submit')
const component = await render(FeedbackComponent, {
imports: [ReactiveFormsModule, MaterialModule],
componentProperties: {
shirtSizes: ['XS', 'S', 'M', 'L', 'XL', 'XXL'],
submitForm: {
// because the output is an `EventEmitter` we must mock `emit`
// the component uses `output.emit` to interact with the parent component
emit: submitSpy,
} as any,
},
})
})