Keith Brewster January 14, 2021
Continuous integration is a discipline. In order to accommodate the iterative-nature of modern software development methodologies, we need to have confidence in the quality of our code. Without a robust testing solution, the feedback cycle becomes slower, we lose certainty in the stability of our software, and we greatly increase the opportunity for human error. Clients often ask us how they can improve their test automation strategy—where to make tradeoffs between the different kinds of tests (unit/integration/E2E), and how to leverage modern tools and practices to get the most value out of their solution. Luckily with the proper procedures in place, we can greatly reduce the surface area for potential issues, allowing us to quickly and efficiently integrate new code with confidence that it will not introduce any regressions.
Unfortunately, developing a holistic testing solution can be a difficult undertaking—without a proficient understanding of the different types of tests and how they fit into our overall strategy, its easy to fall into the trap of writing our tests as an afterthought. This often results in a large suite of unit tests with little focus on how these pieces fit together. While this can give you certainty in the functional correctness of isolated components, it doesn’t provide a great degree of confidence that the application flow is behaving as expected. This is where integration and end-to-end (E2E) tests come in. These are responsible for testing the different integrations and feature flows in your application.
There’s no perfect formula to how these tests should be balanced (though you’ll often see people referencing Google’s recommendation of a 70/20/10 split of unit, integration, and E2E tests). However, balancing your tests should instead be contextual based on the type of application you’re developing. If you find yourself dealing with a lot of business logic or computation, then you’ll probably find yourself leaning heavily towards unit tests. If your project has integrations with many systems or deals with complex interfaces, you’ll find a lot of additional value in writing more integration and E2E tests.
Historically the challenge with E2E testing has been with the tools themselves—they’ve been written on dedicated frameworks outside the scope of what engineers traditionally work with. This creates a need for different skill sets, and companies may not have the resources to take on this additional overhead. The creator of Cypress wanted to provide a tool to break down these boundaries and address the pain points developers face when writing E2E tests.
From the Cypress documentation:
“Our mission is to build a thriving, open source ecosystem that enhances productivity, makes testing an enjoyable experience, and generates developer happiness. We hold ourselves accountable to champion a testing process that actually works.”
This makes Cypress a perfect tool for developer-driven testing; a methodology that gives control of the testing process to developers. This is not to suggest that there isn’t room for QA testers in this model, but putting up an artificial boundary between the development and testing teams will only slow down the feedback cycle and hurt productivity. Engineers should be responsible for the product quality at all levels—accountability shouldn’t end after the feature has been written.
Some of the major benefits of Cypress include:
Managing asynchronicity with automatic waiting. Cypress will automatically wait for async commands or actions to complete (such as waiting for the DOM to load, or for outgoing XHR requests to complete). Considering that many E2E tests involve interfacing with multiple asynchronous systems, managing this asynchronicity is critical.
Powerful test runner. The interactive test runner gives you a powerful tool for debugging your test suites. Cypress takes snapshots as your tests run, and provides you the ability to step back in time and see the state of your application at each step. You can also access familiar debugging tools like Chrome Developer Tools to see console messages and network requests as your tests run.
Stable architecture. Cypress executes in the same run loop of your application, meaning you’re not subject to the quirks of other testing tools that operate outside of the browser and execute remote commands across the network. This also means you have native access to everything in the browser; the window, document tree, service workers, etc.
Operates on the network layer. Cypress works on the network layer, allowing you the ability to control and stub all incoming and outgoing network requests. You can use this to stub responses from third-party services you interact with to help reduce test flakiness.
Huge community support. Cypress has seen massive adoption over the past few years and has a huge community of authors creating custom plugins. Cypress was designed with extensibility in mind, meaning you can shape it to meet your exact requirements. With extensive documentation and tutorials available, it’s easy to quickly bootstrap a setup that works for your application.
Writing Your E2E Tests
In order to identify an appropriate service boundary for an E2E test, you must understand the different user journeys in your application. This will be the best indicator of high-value cases for test automation.
Consider this example user journey of a customer on a website that sells board games:
Launch the site homepage
Navigate to a board game category page
Add an item to the cart
Add a different item to the cart
Open the user cart
Remove the first item
Increase the remaining item’s quantity by one
This is an example of testing a feature flow in your application. While unit tests can help measure the correctness of atomic functions such as adding an item to your cart, this doesn’t provide a lot of context as to how it’s being used as part of the user journey. Identifying these feature flow happy paths is an excellent starting point for introducing E2E tests into your application.
Drawbacks To E2E Testing
While functional tests can give you a stronger degree of confidence in the system behaviors important to your users, it’s important to understand that there are caveats to introducing E2E tests into your solution. It’s important to keep the following things in mind when deciding how to balance the different types of tests in your overall test strategy:
E2E tests contain more points of failure, making it much more difficult to track down which code caused the breakage. It’s important to capture these edge cases with lower-level tests.
Because you’re generally testing the integration between multiple systems, sometimes it can be difficult to write high-fidelity tests. Though you can reduce some of this flakiness with mocking, this comes with a loss of confidence in the integration between what you’re testing and what you’re mocking.
E2E tests are much more expensive to run—while suites of unit tests will generally run very quickly, oftentimes E2E tests are dealing with complex interfaces integrating with multiple systems.
Cypress is a tool that encourages developer-driven testing and helps to empower teams to write more high-value tests. The extensibility provided by its powerful plug-in system coupled with significant community support means that it’s incredibly simple to create a solution custom-fitted to your application requirements. In order to create a robust test automation strategy, it’s important to understand the different types of tests and their roles in creating confidence in the reliability of your product. While unit tests are an important component of a testing strategy, we need to make sure we write integration and E2E tests to realize the full holistic view of our application.
Get started with Cypress today: Installing Cypress | Cypress Documentation