Skip to main content

TDD with Cypress

· 4 min read
Daniel Eshkeri
Zeta Developer

Test-driven development (TDD) is the process of writing an executable specification for your code. You do this by writing a list of test cases, then going through each and writing the test before writing code to pass that test. After you make each test pass, you should refactor in order to keep your code clean and well integrated. Missing the refactor step can result in fragmented and messy code.

  • Write a failing test.
  • Write production code.
  • Refactor

You should start by testing the most crucial parts of your system and if you think of more tests later, you should add them to the list. This rinse and repeat cycle ensures that every part of your code is well tested. Integrating this strategy into your development team can result in massive long term gains in maintainability and reliability.

E2E

Now, let’s pivot to talking about test driven development in the context of UI components and E2E tests (what I am precariously calling TDDIU) (I am ignoring unit tests as there is no difference in TDD in that regard). If you’ve ever tried to employ strict TDDUI testing you’ll know that it’s a pain.

Or if you haven’t you might be sitting there asking “Dan, why is it such a pain?”. Well am I glad you asked!

Imagine you want to create a search bar component. You have your coffee and designs ready, you’ve made a new test file, added your first it block, and then your mind goes blank. “I don’t know what element to query”. This can be a major road block for getting into TDDUI, and often causes people to give up and write the whole component before testing (at least this is what happened to me). But it doesn’t have to be this way.

You must remember that TDD is about making small steps, iterating as you go. Which is no different in TDDUI. For example, you start by testing the container and checking its background colour (then implement), then query the label and see if it’s got the right text (then implement the label), then write a test for the input area (implementing it after; you get the point). Reminding yourself to refactor as you go along.

Something else that can be quite useful is using a custom data tag to query elements. For example, cypress provides the data-cy tag. Using this approach means you don't need to know exactly how you'll create the component when writing the test, which I find significantly reduces the dreaded "coder's block".

These principles apply equally to E2E testing. However, in E2E testing you are concerned with a full user journey - from navigating across pages, or typing in a search bar, and viewing a filtered list. Creating user journeys into tests is great for regression testing, (ensuring that future changes don’t break user flows). In addition, it allows your code to become living documentation that reacts to breakages and bugs. Be aware of the challenges of E2E testing:

  • They can be incredibly complex due to a much larger scope and multiple UI elements.
  • E2E tests are often flaky due to asynchronous events, timing of services, etc.
  • They are slow as a Zebra with a broken leg.

Don’t let those points discourage you though. The core principles remain the same. Write a failing test, implement just enough code to pass, and refactor. The main difference lies in the scope and complexity. While TDDUI introduces new challenges such as querying, asynchronous behaviour, and a broader scope - the iterative, feedback-driven approach of TDD provides the same long-term benefits: a more reliable, maintainable, and well-tested codebase. By embracing these practices, you improve the future-proofing of your application and give your team the courage and sureness to quickly refactor and iterate code without the worry of breaking something crucial.