How Cypress Component Testing Was Born

How I created Cypress component testing.

I always liked end-to-end testing and using a real browser to see the application run. I also love unit tests and probably used every testing framework out there to write the unit tests professionally. But what about component tests? Pieces of front-end code that live somewhere in between E2E and unit tests in the testing pyramid? In my opinion making E2E test runner "understand" components and run them as a "mini" web apps is much simpler than extending unit test synthetic environment. Since I snowboard, I know that going down is much simpler than going uphill!

E2E test runner makes integration/component testing simpler

Note this slide is from my presentation The Shape Of Testing Pyramid shown at AssertJS in 2018. The founders of Cypress.io Brian Mann and Randall Kent were sitting front row during the presentation.

If we could somehow avoid cy.visit(url) and instead use cy.mount(<MyComponent withProp={...} />) to "start" the component for framework X, then we could apply all Cypress command and interact with the component in the full browser just like a real user would. No more "scratchpad" dev pages showing individual custom Date components, etc. Just a browser running your component and the test runner clicking, asserting, spying, intercepting network requests, etc.

Here is an example component test from my blog post My Vision for Component Tests in Cypress.

src/TodoForm.spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react"
import {TodoForm} from "./App"
import {mount} from 'cypress-react-unit-test'

describe('TodoForm', () => {
it('ignores empty input', () => {
const addTodo = cy.stub()
mount(<TodoForm addTodo={addTodo} />)
cy.get('input').type('{enter}')
.then(() => {
expect(addTodo).not.have.been.called
})

cy.get('input').type('hello there{enter}')
.then(() => {
expect(addTodo).to.be.calledWith('hello there')
})
})
})

Notice how we can simply pass stubs and spies and it just works? I also made a mount function for each major framework: React, Vue, Svelte, etc. Angular was difficult, but other Angular developers pushed it over the finish line and it worked.

I made the very first commit to my bahmutov/cypress-react-unit-test repo on December 30, 2017. The first working React component test was made New Year's eve on Dec 31st, 2017. Yup, I was pretty excited about it.

The commits that started Cypress React component testing

Even more specific - the first commit was made almost at midnight at 11:48 PM EST. I should have waited for the strike of midnight clock, Cinderella-style.

The first commit timestamp is 11:48 PM

The first test that worked happened next day Dec 31st, 2018

cypress/integration/hello-world-spec.js
1
2
3
4
5
6
7
8
9
10
11
import { HelloWorld } from '../../src/hello-world.jsx'
import React from 'react'
import { mount } from '../../lib'

/* eslint-env mocha */
describe('HelloWorld component', () => {
it('works', () => {
mount(<HelloWorld />)
cy.contains('Hello World!')
})
})

Of course, it was "Hello World!"

The original component testing was a separate Cypress plugin. Good thing I love writing Cypress plugins. Later Dmitriy Kovalenko who worked at Cypress.io twice helped figure out how to mount the components in the same iframe as the specs, as opposed to the E2E tests that Cypress mounts in a separate iframe. This made things like stubs and spies work, thanks Dmitriy 🙏

Note: I worked a lot on Cypress component testing outside the normal business hours. When I left the company I transferred whatever repos they asked (cypress-react-unit-test, cypress-vue-unit-test, cypress-grep, etc) to the Cypress organization. My plugins had code coverage built-in, stubbing of ES module imports, and other things that I thought were important to have. Since I left Cypress.io I have not worked on these plugins or any Cypress code really, except for reporting found issues.

Cypress issues I opened over the years

Cypress company incorporated the component testing into the main test runner. You can read the official Cypress component testing documentation here.

The naming

I initially named these libraries "cypress-X-unit-test" because in principle you mount the component and interact with it like you would test a small piece of code. Only instead of arguments, you send user commands like clicks. Instead of the result you read the result from the page or other behaviors.

1
2
3
4
// "normal" unit test
it('adds', () => {
expect(add(2,3)).to.equal(5)
})

And here is a unit React component test

1
2
3
4
5
it('logs user in', () => {
mount(LoginComponent)
cy.get('#login').click()
cy.contains('Logged in!')
})

How I see component tests

But I am not too attached to the naming. Everyone pretty much objected to the "unit test" part, and the official name became "Component Testing".

Access the component internal state

I always thought that interacting with the component through the DOM like the real user is the best testing strategy. But sometimes you need to access the internal state of the component. This is where I wrote bahmutov/cypress-react-app-actions but this works just as well with end-to-end tests that access a React we app.

Disclaimer

The timeline of events is how I remember and can reconstruct from the code and blog posts and presentations. If you think I am incorrect, please let me know and bring receipts.

Update 1: Brian Mann responds

Brian Mann responds that he and Chris talked about component testing and even tried it out in the test runner in 2016.

Brian Mann responds to this blog post

To better see, this is the included Slack message

Brian Mann message to Chris about component testing 2016

Lovely. Except it is something they discussed and it never went anywhere. "The company greenlit the decision" is also weird to say. Of course, once I showed it again and again and again it was decided to make it part of the test runner. Again, everyone can see the 480 commits I made by myself with my best friend Renovate into the repo cypress-io/cypress-react-unit-test. Zero Brian, zero Chris until October 13, 2020.

What is really weird in Brian's response is the focus on "It was my idea, even discussed during funding". Never talked to me, I never saw those funding proposals. I would understand "Gleb, your code was bad, it was a prototype, we had to rewrite it all to make Component Testing the first-class feature". I would totally get it, no problem. But the current response... is just so weird for an open-source tool.

Update 2: npm info

Here is something else I realized: you can check out the author of any NPM package either by looking online at package.json file or by using npm info <package name> author command. Let's take a look, shall we?

The listed author of @cypress/react v7 package

Learn more

Over these couple of years I tried to promote Cypress component testing a lot because I truly believe it is a good and powerful testing solution. Here are my blog posts, videos, presentations, and even courses that show why and how to do component testing.