After learning all the ways an Angular web app can perform HTTP communication, I have a better understanding more of Angular's "Tour of Heroes" tutorial app. And even better, I have a better idea of how I could write unit tests for it! That wasn't something covered by the tutorial itself, so writing tests for the tutorial app is my self-assigned extracurricular activity. This follows my previous self-assigned homework, an aesthetic overhaul using Angular Material component library.

An Angular project created via "ng new" command sets up a unit testing pipeline and a few boilerplate tests. "Tour of Heroes" was created this way but it was never touched in the tutorial. Every time we call "ng create" to add a component, that boilerplate also added a simple test to verify that component could be created in the test framework. By the end of the tutorial, we have a handful of these simple creation tests. Most of which would fail, because those components have become more complex than the original boilerplate and the test hasn't been updated to match.

Import Dependencies

First step was to import all the missing dependencies so a component can even be created in the test harness. I first did this mostly by searching for error messages and copying the answers, but it was an unsatisfying approach because I didn't understand what I was doing. To address this, I paused my mindless copy/paste and started studying. I started with Angular Developer Guide to Testing which led to my reactive programming tangent with RxJS. Now I'm back, and I understand how to use import section of TestBed.configureTestingModule to bring in dependencies needed by component under test.

HttpClientModule to HttpClientTestingModule

Once components could be created, I was faced with a long list of HTTP failures. They all had the same cause: components that need to perform HTTP communication imported HttpClientModule, which tried to make actual HTTP calls to a server that isn't online. For unit tests, we need to use HttpClientTestingModule to redirect component HTTP activity to behavior controlled by unit test infrastructure.

Mock Services and overrideComponent

Once tests were in place for services that used HTTP directly, errors came from components that indirectly used HTTP via those services. To keep tests small (the whole point of unit tests) I stubbed out those services with mockups that had just enough behavior to support specific unit tests and no more. Hooking them into the system required using overrideComponent to switch out real services for their simpler mockups.

For my first pass, I wrote mockups directly as TypeScript classes. This is a simpler and more direct first pass, but I think I'm supposed to use "Spy" mechanism provided by Jasmine testing framework for such mockups. Learning to use Spy is still on the to-do list.

Code Coverage Report

Running "ng test --code-coverage" activates code coverage report courtesy of Istanbul. A brief coverage summary is printed to console every time the test suite is executed, but I wanted more details to see where I need to focus my effort. The detailed report is in HTML format under the /coverage/ subdirectory. I'm supposed to go double-click on that file to bring it up in a web browser, but I'm running Angular inside a VSCode Dev Container and I can't "just" go and double-click that file as it is not accessible to my local web browser.

Solution: quickly whip up a static file web server with the serve-static package, letting my local web browser access the report via a port opened on my dev container. I added that file along with a shortcut to my packages.json file so I can bring it up by running "npm run coverage."

100% Is Not 100%

=============================== Coverage summary ===============================
Statements   : 100% ( 82/82 )
Branches     : 100% ( 7/7 )
Functions    : 100% ( 47/47 )
Lines        : 100% ( 75/75 )
================================================================================

These mechanisms helped me to write unit tests that exercised all code paths: 100% code coverage! But this metric is misleading, because there's much more to an Angular app than its TypeScript code.

Many important pieces of functionality live within HTML templates and are not covered by code coverage statistics. That requires accessing ComponentFixture.nativeElement. From there we can navigate the DOM tree, query for elements, and verify they are expected.

There's much more to Angular testing. I haven't even gotten into other things like verifying Angular router functionality but I am getting tired of Tour of Heroes. Time for a change of pace.