A few weeks ago, Pascal Precht wrote a blog article on Testing Services with HTTP with Angular. In this article, we want to discuss more advanced topics on DRY Angular testing techniques using Custom Matchers and Special Helpers.
TABLE OF CONTENTS
These are techniques to make your unit-tests incredibly easy to read and to maintain. And to achieve our learning goals, there are three (3) important testing topics that we want to cover:
- (1) Testing custom Angular Directives
- (2) Building reusable, DRY TestBed Helper methods
- (3) Using Typescript Jasmine Custom Matchers
These techniques are practically undocumented… yet they dramatically improve the quality of our tests.
To whet your appetite, here are some sample DRY tests that we will be showing you how to write:
There a several excellent resources already available that developers can read to learn about Angular testing:
- Angular 2 - Testing Guide (by Gerard Sans)
- Angular 2 - Unit Testing Recipes (by Gerard Sans)
- Testing with the Angular CLI (by Todd Motto, Jurgen Van de Moere)
- Testing Angular 2 Components (by Ken Rimple)
The biggest take-aways from these articles is the singular concept that instead of manually
instantiating and testing classes, Angular developers should consider using
TestBed.configureTestingModule() to prepare an entire test Angular DI environment for each
test module (*.spec.ts).
We would not use
TestBed.configureTestingModule()when we are testing a service that doesn’t have any dependencies. It’d be easier and less verbose to just instantiate using
new. The TestBed is for useful for dependencies and injections.
Consider the traditional approach of manually instantiating and testing a Service or component:
It’s fine to do it this way, because ServiceA obviously does not need anything else to be instantiated; it is a self-contained service without external dependencies.
So assuming that this service won’t get any dependencies in the future, this test is the one we want to write.
Introducing Angular TestBed
The Angular TestBed allows developers to configure ngModules that provide instances/values and use Dependency Injection. This is the same approach developers use in their regular Angular applications.
With the TestBed and its support for for component-lifecycle features, Angular components can be easily instantiated and tested. Here is an example - shown below - that we will continue to use in this article:
Before each test, we want configure a new, fresh testing module with only the providers, components, and directives we need for the current test module.
And notice that we just created a reusable Helper function:
createTestComponent(). This cool utility function will construct an instance of MyComponent [using the configured TestBed] using any custom HTML template you specify.
At first, this complexity may seem like overkill. But let’s consider two critical requirements shown in the sample above:
- MatchMedia instantiation requires an injected BreakPointRegistry instance
- MyComponent instantiation requires an injected MatchMedia instance
Even with these requirements, testing developers should NOT have to worry about all those internals just to test MyComponent. Using ngModule, DI, and Angular… we now don’t have to worry about those details.
This is just like those real-world scenarios where our components, directives, and services will have complex dependencies upon providers and non-trivial construction processes.
And this is where TestBed demonstrates its real value!
We are not using external templates nor any other resources or services that are asynchronous. So we do not discuss the
async()nor the the
1) Testing Directives
With relative ease, developers can find literature on testing Angular Services and Components. Yet the How-to's for testing Directives is oddly not well documented.
Unlike Components with their associated templates, Directives do not have templates. This means that we cannot simply import a Directive and manually instantiate it.
The solution is rather easy! We only need to:
- configure a TestBed that imports and declares the directive(s),
- prepare a shell test Component, and
- use a custom template which uses the Directive selector(s) as attribute(s).
Since Directives usually affect the host element, our tests will check if those changes to the host element are present and valid.
So let’s use the Angular Flex-Layout library as the basis for the following discussions on Directives, Matchers, and TestBed Helpers.
Real-world solutions often provide great examples for reusable techniques.
We will be using both the fxLayout directive and excerpts from the unit test for that directive to explore testing ideas, techniques, and solutions that we can also use in our own tests.
First, let’s import the FlexLayout library into our own tests and prepare to test the fxLayout directive.
You can see the actual testing code in layout.spec.ts.
But don’t jump there yet! Wait until you have finished reading this article.
Configuring the TestBed
Very similar to the TestBed sample shown above, we will configure a testing module but we will
not import an external test component. My test component
TestLayoutComponent is itself defined
within our test (*.spec.ts) module.
Using an internal test component enables each test module
to define and use its own custom test component with custom properties.
Here is the initial configuration:
We now have everything we need to write a Directive test quickly.
Wow! That is pretty easy.
The component has been constructed and prepared with the same processes and DI that your real world application will use.
Let’s first write our test using the traditional long-form… one without custom matchers and the more advanced helper methods.
Since the fxLayout directive will add custom flexbox CSS to the host element, our test logic here will confirm that the initial CSS is correctly assigned.
The traditional approach would probably implement something like this:
In the code above, we
- defined a custom template with bindings to the component property
- use deeply nested references to get access to the native element,
- test each style individually.
All this in one (1) single test. And truly it is not easily read.
Now imagine that our test module has more than 20 individual
That would be a lot of duplicate code.
And there is certainly nothing DRY (“do not repeat yourself”) about such code!
2) Testing with Helpers
Above we explored the standard approach to implementing unit tests… an approach which resulted in verbose, non-reusable code.
Let’s contrast that with the DRY version that we want:
Now this test is much more readable, maintainable, and DRY. We hide all the complexities of:
- forcing change detection,
- accessing the native element, and
- confirming 1…n DOM CSS styles
Those complexities are now encapsulated in a Helper function and a Custom Matcher (respectively).
The custom Helper function expectNativeEl( ) is similar to the standard expect( ) function.
In fact, it is wrapper function that internalizes the
expect( ) call.
It is important to note that these helper methods always return the value of an
And the resulting code change uses a similar notation to our standard training. So
Testing Nested DOM
For more complex DOM access, we can use the DebugElement’s query feature to select nested DOM nodes.
Angular’s DebugElement has several query features:
query(predicate: Predicate<DebugElement>): DebugElement;
queryAll(predicate: Predicate<DebugElement>): DebugElement;
queryAllNodes(predicate: Predicate<DebugNode>): DebugNode;
Please note that all debugging apis are currently experimental.
Consider the following helper function expectDomForQuery( ):
In this example, we actually want to test the nested DOM node that hosts the attribute fxFlex. Using another helper method expectDomForQuery( ) makes that easy.
And the resulting code change is again a similar notation to our standard training:
More Special Helpers
Earlier, we showed a code snapshot that had a special helper
The Flex-Layout library has a responsive engine that supports change detection when a mediaQuery activates (aka when the viewport size changes). The API uses selectors with dot-notation to indicate which values should be used for which mediaQuery:
Testing these features presents several additional requirements:
- mock the window API
- simulate a mediaQuery activation
- trigger fixture.detectChange() after a simulated activation
- hide all these details from individual tests (DRY)
Thankfully the Flex-Layout library actually publishes a MockMatchMedia class. And we use this class in our TestBed configuration below:
Let’s explore three (3) very interesting things are happening in this code:
- configure Dependency Injection providers to override a class with a mock,
- dynamic injection using
- use special helper activateMediaQuery( ) function that hides all these details
(1) Overriding DI Providers
tells the TestBed DI system to provide an instance of the
MockMatchMedia whenever any code
asks for an instance of the
MatchMedia token to be injected via DI.
You can read more about the Angular DI systems here:
Dependency Injection in Angular
(2) Dynamic Injection
Our special helper
activateMediaQuery() needs a dynamic injected instance of the MatchMedia token.
fixture instance, we can access the injector service for our components. With the injector,
we can dynamically get a MockMatchMedia instance using the provider token MatchMedia.
In this case, MatchMedia is both a class and a provider token used for D-injection.
Notice that all this complexity [and construction details] on preparing a MockMatchMedia instance is encapsulated in our TestBed… and the use of the injector is hidden within our special helper.
Now our individual tests simply use the easy special helper function activateMediaQuery( ):
That is very, very cool!
3) Custom Matchers
We have only one more tool [in our testing toolkit] to discuss: Custom Jasmine Matchers.
For those developers not familiar with the concepts of Jasmine matchers, we recommend the online Jasmine documentation:
Remember that matchers are used after the
expect() call and should encapsulate complex logic
and reduce code-clutter in our test code.
This allows our test(s) to remain terse, concise, readable, maintainable, and DRY.
And we should always give our custom matcher functions clear, readable names. E.g. toHaveCssStyles().
Building a TypeScript Matcher
expect(...).toBeTruthy(), we want a custom matcher
- we need to enhance the
- we need to implement custom matchers.
The global Jasmine
expect() method normally returns
<any> value. To use custom matchers
(with types), we want the expect( ) function to support returning either a standard matcher or
a custom matcher.
Here is a teaser that shows how that is done:
We assigned the global expect function reference to a new export that states that
now return references to a NgMatchers interface.
Here are the full implementation details of our
With the above definitions, we can now use
expect(...).toHaveCssStyles(...) without any
We need one to discuss one more addition to our custom-matcher code. Did you notice the call to
getDOM().hasStyle() ? But where does
getDOM come from?
After some inspection of the @angular/core code, we were able to get a reference to the special Angular DOM Adapter:
Please note that the getDOM() is a private Angular API and may change in the future.
Using a Custom Matcher
Now we are golden with features. Let’s import and use our custom Jasmine matcher.
Notice that we must register the custom matchers in a
beforeEach() call to configure the
matchers for each subsequent test. And now everything is ready for the individual tests:
Protractor + e2e
It should be noted that the above sample tests confirm whether CSS styles have been applied correctly to the DOM element. Unit tests perform tests logic and state… but those same tests cannot easily test how those values affect renderings in the UI.
Jasmine unit tests do not test whether the CSS styles or states render the elements in the browser as expected. Nor do they test renderings across different browsers. Those types of visual tests are best performed in e2e testing with Protractor and visual differencing tools.
Perhaps you will say: “Wow, this is cool… but totally overkill!” If you are tempted to say that, then look at all the DRY tests here: layout.spec.ts. You will quickly see the value of using helpers and custom matchers.
You now have the techniques and tools to create your own custom matchers and deliver quality, terse unit tests.
- Custom Matchers: a full set of custom Jasmine Matchers
- Special Helpers: a reusable, importable set of Helpers
- Usage Samples: a full DRY set of usages.
The Flex-Layout Helper functions are actually partial applications (function currying). Here is How to use them.
Angular Master Class at Shopware
Join our upcoming public training!Get a ticket →
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
Testing Services with Http in Angular
Want to learn how to test services in Angular that have an Http dependency? Read more here!
RxJS Master Class and courseware updates
If you've been following us for a while, you're quite aware that we're always striving to provide up-to-date and high-quality...
Advanced caching with RxJS
When building web applications, performance should always be a top priority. One very efficient way to optimize the performance of...
Custom Overlays with Angular's CDK - Part 2
In this follow-up post we demonstrate how to use Angular's CDK to build a custom overlay that looks and feels...
Custom Overlays with Angular's CDK
The Angular Material CDK provides us with tools to build awesome and high-quality Angular components without adopting the Material Design...
Easy Dialogs with Angular Material
Building modals and dialogs isn't easy - if we do it ourselves. Angular Material comes with a powerful dialog service...