Part 2: The Anatomy of an Angular Spec File
By: Jared Fineman
The more things change, the more they stay the same; this adage definitely applies to unit testing. Although we’ve advanced greatly in the world of software development, the fundamentals remain the same. Certain things like AAA, Arrange, Act and Assert will always apply, however, we must learn how to think of them within the world of Angular.
In the previous article, we discussed setting up the infrastructure needed in an Angular Unit Testing Module. So for this article, I’d like to focus on the components that make up your typical Angular Spec File and provide the implementation of the age old concepts of AAA.
In the Arrange portion of our unit test, Angular, leveraging the Jasmine unit testing library, will first describe the component we wish to test by utilizing the describe function, as illustrated below. It’s important to utilize a descriptive name, as this will make reading the results from your unit tests a lot easier.
describe('AppComponent', () => {
Moving on to the Arrange section of our file spec, we utilize the beforeEach function to initialize any configurations that will be needed across all the unit tests in the file. Unique to Angular, however, is the TestBed class, which provides the essential ingredients needed for simulating the construction and initiation of our Angular Module.
beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent, ], imports: [RouterTestingModule], providers: [ ], }).compileComponents(); }));
If you’ll notice, we are using the configureTestingModule method of the TestBed class to initialize our configurations. If we look at the source code, the TestBed class provides additional methods for testing that can be very useful.
static compileComponents(): Promise<any>; static overrideComponent(component: Type<any>, override: MetadataOverride<Component>): typeof TestBed; static overrideTemplate(component: Type<any>, template: string): typeof TestBed;
As their names imply, the above methods can be used to override functionality contained within your module. This may be useful in a case where you want to provide mock data to your component, but your component is coupled with a particular provider; overrideComponent would then give the ability to pass in a fake provider service to inject the mock data.
Some additional things to take note of in the previous code snippet are the RouterTestingModule and the compileComponents function. The RouterTestingModule can be used as a substitute for the usual RouterModule and simulates route navigation without having to actually change url locations. In addition, the RouterTestingModule has a method, withRoutes, that accepts a parameter value of fake routes, which can be useful in various testing scenarios. The compileComponents function is necessary when running your unit tests with a library other than angular-cli. When using angular-cli, however, your unit test code is compiled automatically during the initialization stage.
The Act section of unit testing is where we actually setup our individual unit tests and should look similar to the code below:
it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement;
The createComponentmethod is responsible for creating a copy of the root of our component and returns an object of type ComponentFixture. The fixture of an Angular Unit Test is crucial in that it helps manage and simulate the component’s life cycle. By calling the detectChanges method, the component will detect changes in its environment and bind all of its properties. Keep in mind that our unit tests exist outside the domain of the Angular component lifecycle, therefore, in order for any change to be detected we must first call the detectChanges method.
A closer look at the ComponentFixture class reveals a property of type DebugElement.
export declare class DebugElement extends DebugNode { properties: { [key: string]: any; }; attributes: { [key: string]: string | null; }; classes: { [key: string]: boolean; }; styles: { [key: string]: string | null; }; nativeElement: any; query(predicate: Predicate<DebugElement>): DebugElement; queryAll(predicate: Predicate<DebugElement>): DebugElement[]; triggerEventHandler(eventName: string, eventObj: any): void;
First to notice is that the DebugElement class contains properties such as, properties, attributes, classes, styles that help streamline our unit tests by giving us a wealth of information about our component at our fingertips. The next step is the triggerEventHandler, this method is crucial for simulating DOM events that will be picked up by our component’s event listeners. Last, but not least, is the query method, by far my favorite.
Similar to jQuery, the query method is able to able to search through the DOM and return matching elements. The main difference, and advantage, in its functionality is that the return value is not merely a collection of DOM elements, but of DebugElement objects, making it easier to test nested components. In addition, the query method accepts a parameter of type Predicate<DebugElement>, meaning we can query the DOM using any of the properties found in the DebugElement class.
If that weren’t enough, Angular has provided a class called By, which has further methods to ease the construction of Predicate objects for use in our queries. Below is an illustration, taken from the Angular testing guide, of some examples of this class:
const h2 = fixture.debugElement.query(By.css('h2'));
const directive = fixture.debugElement.query(By.directive(HighlightDirective));
Finally, the last step of AAA, Assert, should look similar to the below:
expect(app.title).toEqual('app');
This is very similar to a typical Jasmine unit test, except we see the power of the Angular unit testing library, as we are directly accessing the title property of the component.
If you like this post, please like it and share it. In addition, if you haven’t checked out Infragistics Ignite UI for Angular, be sure to do so! They’ve got 50+ material-based Angular components to help you code speedy web apps faster.