Arrow Down Arrow Left Arrow Right Arrow Down Arrow Left Arrow Right Arrow Arrow Down Arrow Left Arrow Right Articles Case Study Close CV Facebook GitHub Google+ Menu Information Link LinkedIn x five Interview Location Code Snippet Twitter Tick

Testing Angular faster with Jest

by Michal Pierzchala on March 31, 2017
Published in Open Source 13 Comments
Testing Angular faster with Jest

It struck me how painful experience it was to test an Angular app. And then I realized it can be painless with Jest.

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.

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?

Testing Angular with Jest

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.

Testing Angular with Jest - Watch Mode

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

Note: if you're using npm v2 (which doesn't flat out dependencies) you'll also need to install jest-zone-patch.

You’ll need to add this entry to package.json:

"jest": {
  "preset": "jest-preset-angular",
  "setupTestFrameworkScriptFile": "<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.

Note: setups differ and you may need to tailor the config specifically to your app needs. You should then find README of the preset helpful. It's also worth noting that every option set by preset may be simply overwritten.

Next step – create setupJest.ts. This little guy you can write in TS, also making sure to place it into /src as well. It looks like this:

import 'jest-preset-angular';
import './jestGlobalMocks';

You can see that we’re importing jestGlobalMocks.ts file with patches to our window object (jsdom doesn’t have it all implemented so we need to patch it sometimes).

Example:

const mock = () => {
  let storage = {};
  return {
    getItem: key => key in storage ? storage[key] : null,
    setItem: (key, value) => storage[key] = value || '',
    removeItem: key => delete storage[key],
    clear: () => storage = {},
  };
};

Object.defineProperty(window, 'localStorage', {value: mock()});
Object.defineProperty(window, 'sessionStorage', {value: mock()});
Object.defineProperty(window, 'getComputedStyle', {
  value: () => ['-webkit-appearance']
});

Mocking localStorage is optional, but without mocking getComputedStyle your test won’t run, as Angular checks in which browser it executes. We need to fake it.

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:

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!

 

If you happen to find problems with the setup presented or spot a bug, please file an issue in jest-preset-angular GitHub repository.

About the author

Michal Pierzchala

Michal Pierzchala has been a member of Xfive since 2013. Part of the Jest core team. Passionate about modern web technologies, enthusiast of traveling, sailing and future space exploration.

More articles from Michal

Comments

Wilgert Velinga April 1, 2017

Jest looks like a big improvement over Karma. But actually something exists that is even better into terms of feedback speed and quality: wallabyjs. Try it out!

kpax April 3, 2017

I can't seem to figure out how to get webpack to run through it's loaders first, like angular2-template-loader to do what your preprocessor is doing. Is there a way to get jest to kick off a webpack bundle first?

Michal Pierzchala April 4, 2017

kpax Running your code through Jest and Webpack are two different things. One doesn’t know about the other. Here you’ll find a Webpack tutorial on official Jest docs. You’ll probably need to adjust moduleNameMapper to suite your needs. You can also submit an issue on jest-preset-angular and we’ll see what can be done. Good luck!

Dominic Watson April 4, 2017

Feel like I'm being dumb and missing something, but is this for ejected CLIs?
Using on a project out of the box I get: `SyntaxError: Unexpected token import` as it runs through each of the tests.

Brady Isom April 4, 2017

I was excited about trying to add Jest to my Angular 4.x/AngularCli project. Thank you for the post. However, In following the instructions, I am seeing an error when Jest tries to load the setupJest.ts file:

/Users/bradyisom/Development/buzz/src/setupJest.ts:1
({"Object.":function(module,exports,require,__dirname,__filename,global,jest){require('ts-jest').install();import 'jest-preset-angular';
^^^^^^
SyntaxError: Unexpected token import

at transformAndBuildScript (node_modules/jest-runtime/build/transform.js:320:12)
at handle (node_modules/worker-farm/lib/child/index.js:41:8)
at process. (node_modules/worker-farm/lib/child/index.js:47:3)
at emitTwo (events.js:106:13)

Any ideas on what is wrong here?

Michal Pierzchala April 5, 2017

Dominic, Brady, just for the record, this was resolved in jest-preset-angular repo: https://github.com/thymikee/jest-preset-angular/issues/4

hotelyorba April 5, 2017

Do you have a github repo where we can see some examples of how to test angular components and services using Jest?

Michal Pierzchala April 8, 2017

hotelyorba, I have an example directory in jest-preset-angular repo:
https://github.com/thymikee/jest-preset-angular/tree/master/example
But in general you can test components, services, reducers, etc the same way as with Jasmine, provided that you're not using unsupported methods (like jasmine.createSpyObj()).

danAnderl April 15, 2017

Hello there,
very nice article. i manage to execute 208 tests in around 30 secs in the best case. which is quite good, but i want it even faster. the problem seems to be in some initial step, as only the first tests take long (6 - 10 secs -> each worker ) and the following up are really quick.

-> watch mode 2 specs changed:

PASS src/app/features/geofencing/geofencing.component.spec.ts (5.593s)

PASS src/app/features/geofencing/geo-definition/geo-definition.component.spec.ts (9.75s)

Test Suites: 2 passed, 2 total
Tests: 16 passed, 16 total
Snapshots: 0 total
Time: 10.709s, estimated 11s

-> watchAll -w=8

Test Suites: 5 failed, 41 passed, 46 total
Tests: 202 passed, 202 total
Snapshots: 0 total
Time: 31.418s

do you have any idea to investigate what takes so long initially? i am using uptodate versions of ng (4.0.1) and ng-cli (1.0.0)

Cheers Andi

Michal Pierzchala April 18, 2017

Hey danAnderl!
The following runs are faster, because the cache is "warm". On the first run Jest scans all necessary files and transform them from TypeScript to JS. Transforming takes some time, so the results (JS files) are cached – this way the files are only transformed through tsc (or any other transformer, e.g. babel) only once, and after they're changed.
Glad it works for you!

Jan April 20, 2017

Thanks for the great article and project ... I still have a question concerning Snapshot testing ... is there an equivalent to the react-renderer i can use in Angular projects?

Michal Pierzchala April 22, 2017

Jan, working on it!

Michal Pierzchala April 22, 2017

Hey folks, it's now possible to snapshot your Angular components: https://github.com/thymikee/jest-preset-angular/pull/24
Happy testing! :)

Would you like to add something?

All fields are required. Your email address will not be published.

Struggling with lack of time or resources for web development? We can help

Get a Free Quote

More from the blog

Submit a Project