Testing Angular Faster with Jest
- Development
- Michal Pierzchala
- 5 min read
It struck me how painful experience it was to test an Angular app. And then I realized it can be painless with Jest.
This article was last updated on Sep 23, 2019, so the setup works with the latest Angular versions. If you still have troubles with your project setup, please open an issue in the jest-preset-angular
Note: This article assumes you are using an Angular CLI v1.0 generated project from Angular v4.0.0. But it too works on Angular v2.x with CLI in beta.
Despite Angular being a comprehensive framework with lots of default abstractions: e.g. custom JIT and AOT compilers, deep RxJS and Zone.js integration, it’s still possible to work outside of its toolbox. Interestingly, it’s possible to change the default test runner. This post will tell you why, and how you can do it.
Get a Free Audit for Your Website
If you ever felt blue, tired, hopeless while running your Angular app slow test suite with Karma in your favourite browser (and I’m not talking about injecting every possible component into your test file), keep reading.
What if I tell you, that you can:
- run your Angular tests without a browser
- run test suite several times faster
- rerun instantly only tests related to latest code changes?
Meet Jest
Jest is a painless JavaScript testing platform. If this doesn’t tell you much, it’s probably because you had to spend all your free time on running slow Karma tests ????. Let me introduce it briefly:
- Jest is a testing platform, widely adapted by many large companies and swiftly adopted by the React community.
- Sits on top of Jasmine, so the API is nearly identical.
- Has all of it’s API documented, along with guides, examples and helpful community on forums like Reactiflux and Stack Overflow.
- Focuses on Developer Experience (speed and ease of use is the first priority.)
- Provides meaningful error messages.
- Runs on Continuous Integration servers without extra tooling (abstracting the DOM with jsdom library.)
- Provides code coverage out of the box.
- Integrates with Babel and TypeScript seamlessly.
And what’s most important is that it provides a smart, immersive watch mode. The watch mode runs only tests affected by git file changes – it also runs failed tests first and is able to bail on first error so the feedback loop is ~10x faster than with Karma, depending on test suite size.
To the point – I want my Angular tests faster now!
We need to install necessary dependencies (I’m using yarn, but if you fancy npm, no problems):
$ yarn add --dev jest jest-preset-angular @types/jest
where:
- jest – Jest testing platform
- jest-preset-angular – configuration preset with common settings setup for you
- @types/jest – Jest typings
You’ll need to add this entry to package.json:
"jest": {
"preset": "jest-preset-angular",
"setupFilesAfterEnv": ["<rootDir>/src/setupJest.ts"]
}
This is most likely all configuration you’ll need. I’ve extracted common configuration options into jest-preset-angular package and it should just work™ for most cases. Please go to the library documentation for the up-to-date instructions on how to set it up correctly for your project.
You’re now ready to add this to your npm scripts:
"test": "jest",
"test:watch": "jest --watch",
…and change the way you test Angular apps forever.
Oh, one more thing. Forget about installing PhantomJS on your CI:
"test:ci": "jest --runInBand",
It’s worth noting that CI servers usually run your code on single core, so parallelization may slow your tests down. If you’re experiencing such behavior, use –runInBand flag to tell Jest explicitly that you want to run tests one-by-one (just like Karma or Mocha).
Caveats
Of course, there are some. But surprisingly not many.
Migration from Jasmine
We’ll need to migrate some of Jasmine calls to Jest. Most of the API is similar but there are slight differences.
Below are listed required changes to be made in your codebase.
- jasmine.createSpyObj(‘name’, [‘key’]) –> jest.fn({key: jest.fn()})
- remove @types/jasmine module (duplicate TS declarations from @types/jest)
After porting jasmine.createSpyObj() you can come back later to migrate rest of the functions, which are optional at the time of writing (this may change in the future):
- jasmine.createSpy(‘name’) –> jest.fn()
- and.returnValue() –> mockReturnValue()
- spyOn(…).and.callFake(() => {}) –> jest.spyOn(…).mockImplementation(() => {})
- Asymmetric matchers: jasmine.any, jasmine.objectContaining, etc. –> expect.any, expect.objectContaining
Farewell browser, our tests now run in jsdom
You can also bump into APIs available in browsers but not in jsdom, like htmlElement.closest or window.localStorage (which we just mocked in jestGlobalMocks.ts).
Zone.js messy error stack trace
I couldn’t force Zone to output shorter and more meaningful error stack traces. As a workaround you can add Error.stackTraceLimit = 2; to your setupJest.ts or whatever number suites you. I find it useful for most cases.
IDE integrations
Be sure to also check these awesome extensions to ease testing experience even more:
- vscode-jest
- WebStorm
- Atom (work in progress)
Summary
I was truly amazed that it’s actually possible to integrate a 3rd party testing tool not running in browser, like Jest, into Angular. Couple of days ago I still thought that it will be impossible to do so within reasonable amount of time and it’s just not worth the effort, but turned out it was so worth it.
Our app’s whole test suite of 35 files with ~100 tests executed 2.5x faster than with Karma. We can also use snapshot testing. What’s most important though, we now have powerful watch mode, rerunning tests instantly. And all of this without the need of compiling the app or running a dev server.
So if you care about your testing experience, do yourself a favor and run Angular tests with Jest. You’ll love it.
And don’t forget to star it on GitHub!