DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Updated on

How to Speed Up Angular Tests

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

By default, Angular tests run very slowly because a lot of dependencies are injected and components being tested are reloaded when each test is run.

As a result, you get tests that take 30 minutes or more to run.

It is bad enough that an issue is opened on GitHub about it https://github.com/angular/angular/issues/12409.

To remedy this, we have to load all the dependencies only once when each test file is executed and then let the test run without reloading everything again.

We make a new spec file called spec-helper.spec.ts and add the following:

export const beforeTest=()=> {
  beforeAll((done)=> (async ()=> {
    TestBed.resetTestingModule();
    TestBed.configureTestingModule({
      declarations: [
        AppComponent,
        LoginPageComponent,
        MonitorPageComponent,
        SettingsPageComponent,
        AccountsPageComponent,
        TopBarComponent,
        SignUpPageComponent,
        ImagesPageComponent,
        AccountDialogComponent
      ],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        MatButtonModule,
        MatCheckboxModule,
        MatToolbarModule,
        MatInputModule,
        MatSidenavModule,
        MatIconModule,
        MatListModule,
        MatTableModule,
        MatDialogModule,
        MatGridListModule,
        RouterTestingModule.withRoutes(appRoutes),
        FormsModule,
        StoreModule.forRoot({
          openSideNav: openSideNavReducer
        }),
        ClickOutsideModule,
        HttpClientTestingModule,
        CustomFormsModule
      ],
      providers: [
        UserService,
        SecurityService,
        AuthenticatedGuard,
        {
          provide: HTTP_INTERCEPTORS,
          useClass: HttpReqInterceptor,
          multi: true
        },
        {
          provide: MatDialogRef,
          useValue: {}
        },
        {
          provide: MAT_DIALOG_DATA,
          useValue: []
        },
      ],
    })
    await TestBed.compileComponents();
    // prevent Angular from resetting testing module
    TestBed.resetTestingModule=()=> TestBed;
  })().then(done).catch(done.fail));
}
Enter fullscreen mode Exit fullscreen mode

given all these items exist in our code and libraries. The file will include all the dependencies and code in our Angular module.

Then we import the file in our specs and run the beforeTest function that we exported.

After this, our unit tests will run orders of magnitude faster, from 30 minutes down to a few seconds.

Also, now we don't have to worry about including our dependencies in every test file and worry about missing dependencies for every test module.

Discussion (22)

Collapse
layzee profile image
Lars Gyrup Brink Nielsen • Edited on

The worst low performance issue occurs when components have external stylesheet and template files. For every test case, the compiler has to get access to, open and read two files per component, before it can even compile the component. So either use inline templates and styles or use the hack of preventing the Angular testing module from resetting between test cases.

Apparently, Ivy should make testing faster, but I haven't seen any facts about this yet.

If we just prevent the Angular testing module from resetting in beforeEach, we take care of the worst performance issue in tests and we don't have to add all declarables and dependencies to a single Angular testing module configuration which is hard to maintain and does not work well for all tests.

I used the hack with good results back in Angular version 5. I haven't tried it since then as I favor not using the TestBed at all for a lot of tests. One big caveat is that services and other dependencies are reused between test cases. So stateful service instances are not reset.

Collapse
joerter profile image
John Oerter • Edited on

Do you know if using some of the testing libraries such as angular-testing-library or spectator help with this issue? I haven't used either of them, but am beginning to experiment. Angular Testing Library at least looks like it may expose components in a different way that gets around this issue.

I too have stopped using the TestBed for most tests and simply test components by treating them as simple classes and asserting against their public properties, but am beginning to look at these other libraries as an easier and quicker way to test against the DOM

Collapse
layzee profile image
Lars Gyrup Brink Nielsen • Edited on

Both of them wrap TestBed so they will have exactly the same problems. We did discuss adding ng-bullet to Spectator, but decided to wait for Ivy.

Thread Thread
zaky7 profile image
Zakir

I have seen even after Ivy, ng-bullet is still helps in speed up the tests. Is the discussion still open?

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

I'm not sure. You're welcome to file an issue to Spectator.

Collapse
aumayeung profile image
John Au-Yeung Author

Pretty much every Angular component have these files.

Honestly, It's like the Angular team didn't test it at all with lots of components in a module.

Are there any alternatives to that doesn't involve adding recreating the module in every file?

It's not only the speed, but having to recreate the module code for every test file is also a pain.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Oh, you mean having to setup all the needed Angular module imports and declarations? Are you aware of SCAMs?

One advantage is that you can often just import a component's SCAM to test it. It's so unexpectedly easy that Spectator had to put in a special option to undo some of its normal affordances to make component tests easier to set up.

Thread Thread
aumayeung profile image
John Au-Yeung Author

I'm not aware of that. Too late for existing apps probably.

But looks useful for new apps.

I already refactored the TestBed to its own function so it can be imported and run anywhere, that's how I solved it.

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

Thanks. Of course, there's also the option to use shallow component tests, where we include no or only some view child components in our tests:
angular.io/guide/testing#nested-co...

Thread Thread
aumayeung profile image
John Au-Yeung Author

But we still have to put dependencies in declarations right?

Thread Thread
layzee profile image
Lars Gyrup Brink Nielsen

Not with shallow components tests using NO_ERRORS_SCHEMA.

Thread Thread
aumayeung profile image
John Au-Yeung Author

That's great. It's annoying to have to put everything in the TestBed module.

Anything in Angular is more complex than the other frameworks.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Details on how Ivy will improve the performance issues discussed in this thread:
github.com/angular/angular/issues/...

Collapse
joerter profile image
John Oerter

Interesting solution! I noticed several people mentioning ng-bullet in the issue you linked. Have you tried that? I'm curious to know more about your experiences.

Collapse
aumayeung profile image
John Au-Yeung Author

I looked at Stack Overflow and this is the only way I found.

I haven't tried ng-bullet. Did people say it's good?

Collapse
joerter profile image
John Oerter

Yes, it seemed to solve a lot of people's issues.

Thread Thread
aumayeung profile image
John Au-Yeung Author

I have to check it out.

Thanks for finding that.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Details on what takes up the most time in Angular tests:
gist.github.com/Quramy/1dd5bed0bce...

Collapse
adisreyaj profile image
Adithya Sreyaj

Interesting...

Collapse
layzee profile image
Lars Gyrup Brink Nielsen
Collapse
layzee profile image
Lars Gyrup Brink Nielsen • Edited on

"Next-level testing in Angular Ivy version 9" by yours truly
indepth.dev/next-level-testing-in-...

Collapse
aumayeung profile image
John Au-Yeung Author

Thanks. That's a useful find.