fbpx Skip to content

Aquent | DEV6

Angular: Unit Testing Tips and Tricks

Written by: Tyler Padley

One of the better upgrades in the Angular 2 framework is its capacity for unit testing. From the reactive forms module to the new TestBed, it’s easier than ever to write quick, meaningful tests and run them in any project.

While there are many introductory articles around unit testing and the official Angular documentation does a good job illustrating the concepts, the goal of this article is to highlight some of the lesser known, but still handy features in Angular 2’s testing framework.

This article assumes you have some basic knowledge of Angular 2’s unit testing capabilities, including TestBed, Karma and Jasmine.

Testing in Isolation

One of the more difficult things to do when testing is to ensure you are testing in isolation. Eliminating external dependencies will give you more stable, repeatable test results. Angular 2 provides a few techniques for handling this.

The handiest shortcut I’ve seen when writing unit tests is Angular’s CUSTOM_ELEMENTS_SCHEMA constant. When added to a TestBed’s schemas collection, this schema will allow the compilation of your test environment without having to provide declarations for all of the elements contained within the template. Here is an example usage:

TestBed.configureTestingModule({
    imports: [RouterTestingModule],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    declarations: [TestComponent],
});

What this will do, is safely ignore any custom elements in TestComponent’s template, crucial for ensuring you’re testing the component in isolation. However, there are a few gotchas with this schema, as it only allows named tags and attributes with the hyphen (-) in their name, restricting users to industry-standard naming conventions.

This should suffice for most use cases, but sometimes you may need to provide specific data, or eliminate the template altogether. TestBed has a handy feature that will allow you to override most aspects of the component through overrideComponent.

This feature will allow you to add, remove or set any property that is configured via the component’s decorator, including the template. Here is an example below:

fixture = TestBed.overrideComponent(OverriddenComponent, {
    set: {
        template: `<h1>Test</h1>`
    }
}).createComponent(OverriddenComponent);

Setting the component template to a simple string will remove the requirement of providing all necessary dependencies and allow you to test the underlying component code. You can also replace internal declarations to match anything new you want to test within the component.

Testing Inputs

With reactive forms, it becomes relatively straightforward to test your inputs; setting values can be done on a form or control level, validity flags are set right away, and you can test the resulting submission all with built in methods.

However, one of the more underrated new features of the framework is the valueChanges observable. These can be subscribed at a form or control level, and then allow you to leverage the power of the RXJS Observable framework to add things like debouncing, filters and formatters. Here’s an example below:

this.inputName.valueChanges
    .debounceTime(250)
    .takeWhile(() => this.active)
    .subscribe(entry => {
        //process values here
    });

When testing this type of code, the values have to be entered into the form control itself. Fortunately, Angular’s ComponentFixture has a solution for this:

Let fixture:ComponentFixture = TestBed.createComponent(TestComponent);

Let input = fixture.debugElement.query(By.css("input"));
input.nativeElement.value = testValue;
dispatchEvent(input.nativeElement, "input");

The debugElement allows you to run various element queries to access components within the template, and from there it’s just a matter of setting the value, then using the testing framework’s “dispatchEvent” method to send that data on through to the Observable. This can also apply to combo boxes, radio buttons and other form controls.

This technique is also critical for testing directive instances as Angular’s testing framework allows you to query by directive:

let inputElement: DebugElement = fixture.debugElement.query(By.directive(TestDirective));
directive = inputElement.injector.get(TestDirective);

This technique will get you an element reference, however, accessing the element’s injector can get you access to the directive instance provided to that element.

Route Values

Often you will find yourself writing code based on parameters provided by the route. While Angular’s RouterTestingModule is handy for testing routes and routing directives such as resolves and guards, if you’re just looking to capture route data, you can easily mock what you’re after using the RXJS framework and Subject stubs.

Typically, you can replace the ActivatedRoute injection with the required mock object and then use standard observable techniques to emit data. Here’s an example below:

let mockRoute: any = { snapshot: {}};

mockRoute.parent = { params: new Subject<any>()};
mockRoute.params = new Subject<any>();
mockRoute.queryParams = new Subject<any>();


TestBed.configureTestingModule({
    imports: [RouterTestingModule.withRoutes([])],
    providers: [{ provide: ActivatedRoute, useValue: mockRoute }],
    declarations: [ TestComponent ]
})

The above code will replace the ActivatedRoute injection with the mock, stubbing out the parent params (for route parameters from the parent), the route params, and the route query params. Sending test data through is simply a matter of triggering an Observable.next within your test case.

mockRoute.parent.params.next({ paramName: 17 });
mockRoute.params.next({ paramName: "test"});

This approach is much simpler than declaring acceptable routes (though you will want separate unit tests to ensure your routing use cases are valid).

One final note, is that since these are observable event dispatches you will want to call complete() at the end of your test, and, if using jasmine, wrap your test cases in an async()call.

These examples are just a brief summary of some simple solutions to common testing problems I’ve encountered thus far in testing with Angular 2. Feel free to leave comments with tips and suggestions of your own!

Sign up for our Angular Course

$1,995 CAD – 4 Days

View Course Details