Techblog: Unit testing Angular components with Cypress
Cypress is a test runner mainly focused on end-to-end tests. It works much like protractor, but in its own way. It doesn’t use webdriver which makes it quicker, but it also doesn’t make it cross-browser as it is running your tests in Chrome.
In this blogpost I’ll show you how I got it to run unit tests, but people only interested in the end result can skip ahead to the `Getting it all up & running`.
What's the minimal Angular web app with bootstrap? I am trying to recreate same component tests as in https://t.co/QcWJcutty7 or https://t.co/dgIWbKiC9P and there seems to be a lot of steps to do for Angular 🙁— Gleb Bahmutov (@bahmutov) 18 januari 2018
The reason I was surprised is that I could not believe it would be that hard. But boy was I wrong. Months passed and still no progress on the Github repository, where I was expecting a quick reply from anybody in the Angular community. In July I finally found the time to have a better look at what was going wrong.
After cloning the repository and running the cypress spec file I could reproduce the issue easily: The component still wouldn’t be rendered correctly to the target iframe. Instead some other html fragment was shown that didn’t reveal its origin. After changing the
templateUrl property in the @Component annotation to an inline
template property the issue seemed resolved. I didn’t know of an easy way to fix this issue and posted my intermediate results on github.
Before I woke up the next morning Aivan Monceller had solved the resource loading issue by modifying the ts-loader cypress plugin to inline the resources using the webpack plugin ‘angular2-template-loader’!
Getting it all up & running
1. Create a new angular app using the angular CLI:
ng new angular-cypress-unit
And change to the newly created directory:
2. Install Cypress and all the dependencies we’ll need for it to compile the Angular app
npm install — save-dev cypress @cypress/webpack-preprocessor angular2-template-loader ts-loader
3. Add Cypress scripts to your package.json
4. Run Cypress to create the initial directory structure in ./cypress:
npm run cy:open
5. Copy the spec file from Gleb Bahmutov’s repository https://github.com/bahmutov/cypress-angular-unit-test/blob/master/cypress/integration/spec.ts in ./cypress/integration. You can optionally remove the ./cypress/integration/example directory that is generated by default with example tests.
6. You will also need to copy the files from https://github.com/bahmutov/cypress-angular-unit-test/tree/master/cypress/plugins to your local ./cypress/plugins directory as it contains the modified Cypress plugin (see the original) that takes care of Typescript and angular template inlining preprocessing. The plugin extends the default Cypress webpack configuration.
7. Place a tsconfig.json file in the ./cypress directory that sets the scope of the typescript compiler to integration/*.ts and sets some compiler options to emit decorator metadata that is required for Angular JIT compiling
8. If we run Cypress again it finally compiles correctly, but the Angular dependency injector gives the following error:
Error: Can’t resolve all parameters for ApplicationModule: (?).
This is because we need the reflection polyfill for the JIT compiling. You can import it by adding the following line to the spec.ts file. Make sure that this import statement is before any @angular import statement.
// Required for JIT in NG-7
By now you should be able to run your first cypress unit test! You might need to change the assertions in the original spec file to match your project name.
Using the Angular private API
To make the individual rendering of the components work without bootstrapping a whole Angular application, we could use the private Angular API renderComponent en compileComponent as discussed by Uri Shaked in his blog post on how to make Ivy work in Stackblitz. All private Angular APIs are prefixed with a
ɵ as a kind reminder that they are private.
Private API means: can be changed or removed without prior notice!
Using the Angular private API make the code much more readable except for line 13 where we need to find the component definition in the annotations, but this could be easily extracted away from our test code.
Getting Cypress up & running for Angular unit tests is indeed not as easily as a developer would want. We haven’t even looked at possible zone.js /(fake) async issues. The main problems were getting compiled the way Angular does and rendering a single component instead of the whole application.
The build process using the webpack preprocessor for Cypress could possible be handled by @ngtools/webpack, which would make it less error prone.
A fully working repository, which is a fork of Gleb Bahmutovs Angular 5 version can be found at https://github.com/meDavid/cypress-angular-unit-test/tree/angular7