Performance Angular unit testing

I’m loving Angular, but running unit tests on Karma gets my nerves. It’s too slow for me.

In this post, I explain mechanics under Angular’s testing module and how to improve the performance.

What makes my tests slow?

To evaluate Angular unit testing performance I captured the CPU profiling with running Karma.

The above profiling was captured under the following condition:

  • angular version: 4.0.3
  • scaffold with angular-cli
  • put 300 components to app module
  • run 15 specs on karma and chrome
/* app.component.spec.ts */
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
AppModule, // This module has about 300 components.
],
}).compileComponents();
}));
for (let x = 0; x < 15; x++) {
it('should render title in a h1 tag ' + x, async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('app works!');
}));
}
});

The above pictures indicates that the execution time of compilation components accounts for over 70%.

The test proceeds as follows:

  1. Start a spec
  2. Configure testingModule
  3. Create component fixture for AppComponent
  4. Compile all components and parse their templates included in AppModule
  5. Create a module factory using the compilation result
  6. Create an AppComponent
  7. Assertion
  8. End the spec
  9. Start the next spec…

The main point is that the compilation all components is executed in every specs.

Imagine bootstrapping your application in JiT compilation mode dozen of times. It’s so terrible.

Improvement performance

Isolated component tests

Make component unit tests independent of other modules. This technique is called “isolated unit tests”.

If you want detail, see https://angular.io/docs/ts/latest/guide/testing.html#!#isolated-component-tests .

Separate your modules

Check the module imported from TestBed is really necessary for your test?The compilation time gets longer as the module gets larger. If the module includes a lot of unused components, you can separate them into another module. And purge the separated module from TestBed.

Configure TestBed’s compiler

This technique is so hacky.

In many cases, TestBed has the same configuration for each specs in a test code.
For example:

/* MyComponent.component.spec.ts */
describe('MyComponent', () => {
let fixture: ComponentFixuture<MyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ AppModule ]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
}));
it('should render something when someCondition is true', async(() => {
fixture.componentInstance.someCondition = true;
/* assertion something is rendered... */
}));
it('should not render something when someCondition is false', async(() => {
fixture.componentInstance.someCondition = false;
const compiled = fixture.debugElement.nativeElement;
/* assertion something is not rendered... */
}));
});

In the above example, the both specs need AppModule (and MyComponent included it). TestBed creates dynamic testing modules twice and the created testing modules have the same declaration because they depend on the same module and components. In other words, the compiled module factories are reusable in this file.

So if you customise the compiler used by TestBed to be able to turn use the executed compilation results, you can reduce the total compilation time.
I’ve created a helper library to do this(https://github.com/Quramy/ngx-zombie-compiler).

But don’t forget that this method has a risk of destroying the idempotency of the unit testing. If you practice this technique, take the risk into consideration.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.