Marmicode
Blog Post
Younes Jaaidi

Testing Angular Components Using Cypress

by Younes Jaaidi • 
Feb 18, 2021 • 6 minutes
Testing Angular Components Using Cypress

Whatever framework you are using, and even without frameworks, component testing is a challenging topic as there is no one-size-fits-all approach.

One of the first challenges is picking a testing framework. In this blog post, we will focus on , and which are gaining popularity in both JavaScript and Angular communities.

Jest Limitations

While staying simple and easy to set up compared to other tools, Jest provides a nice panel of features like its feature-rich API, the interactive watch mode, human-readable reports, native parallelization, and its flexible mocking system. These Developer Experience enhancements encourage us to write tests and can act as efficiency-boosters.

All of these features will generally make Jest a nice fit for testing behavior, business logic, and interactions with other components and services, but when it comes to DOM testing, a new challenge arises.

Like any other unit under test, a component, or a block has inputs, outputs, and interactions with other units. However, . This particular interaction will often have the following properties:

A good example of this is a component that hides some content depending on a condition. As you probably imagine, this could be implemented in lots of different ways:

<div>
  <h1 *ngIf="condition">Marmicode</h1>
</div>

or

<div *ngIf="condition">
  <h1>Marmicode</h1>
</div>

or

<div>
  <h1 [class.hidden]="!condition">Marmicode</h1>
</div>

or

<div [class.hidden]="!condition">
  <h1>Marmicode</h1>
</div>

Testing the existence of the h1 or the presence of the hidden class on the div or the h1 would mean that we are testing an implementation detail. Are we even sure that the hidden class really exists?

What we really want to test is the visibility of the h1.

Luckily, there is a library extending Jest with DOM matchers that help to solve this issue:

Still, even though DOM testing with Jest is possible, the Developer eXperience is not at its best. Indeed, Jest is not running in a browser so we can't produce a visual output .

Cypress

Cypress initially focuses on Functional, End-to-End, Smoke tests, etc... I just call them . My definition of a wide test is any test that matches one of the following conditions:

In opposition to the Jest limitations presented before, Cypress offers the following features and advantages:

Yet, as mentioned before, Cypress focuses on wide tests. It is designed to visit a web application at a given URL using cy.visit() then interact with it at your will. This is when we start wondering how we could test a single component or block.

Cypress + Storybook Combination Limitations

One of the most common approaches is combining Cypress with UI component explorers like . Interestingly, every Storybook story is loaded in a , meaning that we can of any story directly using Cypress.

cy.visit('/iframe.html?id=blogpost--default');
cy.get('h1').contains('Testing Angular Components Using Cypress')

We finally managed to isolate components and blocks in Cypress but this approach also has its limitations:

Cypress Component Testing

As you might have already guessed, there is a better way. Cypress provides a new feature called allowing us, using some framework-specific libraries, to mount components directly in the tests without having to visit any page.

@jscutlery/cypress-mount

Imagine if we could write our tests in Cypress somehow like this?

import { TitleComponent } from '.../src/title.component';

it('should display default title', () => {
  mount(TitleComponent);
  cy.get('h1').contains('👋 Welcome');
});

Guess what! Now, you can, thanks to our new library. @jscutlery/cypress-mount has an opinionated approach aiming to reduce the learning curve and help making trustworthy and maintainable tests.

Once set up, not only you can mount and test any Angular component but @jscutlery/cypress-mount comes with some useful features. Here are some of them:

Control Inputs

We can of course .

mount(TitleComponent, {
  inputs: {appName: 'Marmicode'}
});
cy.get('h1').contains('👋 Welcome to Marmicode');

Override Providers

We can .

mount(TitleComponent, {
  providers: [
    {
      provide: Settings,
      useValue: {greetings: '🇫🇷 Bienvenue'}
    }
  ]
});
cy.get('h1').contains('🇫🇷 Bienvenue');

Mount Template

We can dynamically mount any .

mount(`<mc-title></mc-title>`, {
  imports: [TitleModule],
})

Storybook & Component Story Format support

@jscutlery/cypress-mount also supports , meaning that we can reuse and in Cypress. Ain't that hip!?

import { Love } from '.../love.stories.ts';

describe('Love', () => {
  it('should show some love', () => {
    mountStory(Love);
    cy.get('h1').contains('❤️');
  });
});

Try it yourself

If you want to give it a quick try, you can retrieve the jscutlery/test-utils monorepo and run some demo tests locally.

git clone https://github.com/jscutlery/test-utils
npm install
npx nx e2e cypress-mount-integration --watch

You will then find some examples here

Where to go from here?

As mentioned at the beginning of this blog post, there is no one-size-fits-all approach. Hence, don't run and migrate all your component tests to Cypress Component Testing 😉. Also, it is not unusual to write tests for one component with both Jest and Cypress Component Testing as they cover different parts.

While Jest and its tremendous ecosystem is still often a good choice, you should consider Cypress Component Testing when: