DEV Community

Natalia D
Natalia D

Posted on • Updated on

Effective Frontend Test Strategy and some notes about Cypress

Image description

As a developer, I frequently find myself working on projects that lack a dedicated testing team or resources. As someone who has spent a noticeable amount of time performing manual testing, fixing frontend unit tests, and writing end-to-end Cypress tests to ensure the highest code quality possible under high business pressure, I have developed some strong opinions about organizing testing strategies in such circumstances.

Time for testing

From my experience, usually there is no time specifically reserved for tech debt or automated testing. Generally, every day the business is like:

Image description

There is some time, though, during which developers have to do regression before they show the code to the client/product manager. However, this process can be time-consuming and overwhelming, taking up several hours or even an entire day!

I'll be talking about the case when you are often asked to do manual regression and you don't have much time to make your life easier, only little time slots here and there (e.g. when the board is not so busy, you've made some PR's and have something to do while you wait for other devs to review them).

Test pyramid

Let's talk about tests for React or Angular. To spend less time developing them you will want to have more presentation (dumb) components and less smart components.

If you have a lot of complicated web forms it makes sense to use Storybook and write a storybook test for each such form realised as a dumb component.

When you have written a bunch of tests for smart React/Angular components, you might find out that it's hard to maintain them, because each test has a lot of dependencies and json mocks that you have to update quite often. Given that bringing a test back to life might take 2-3 hours (and I haven't even included in this estimation time for other devs to code review it) my recommendation is to save efforts and skip tests for most of the features. Think of the most critical parts that your app has instead. E.g. if you do payment operations through your app it makes sense to write as much tests as you can to make sure that money goes where it's supposed to! In other words, before you create a test for a smart component/service/module think: "is it worth supporting such test? How often it's used by other modules? How critical is the feature?"

Some developers might argue that you can make make your code more testable: e.g. extract some pure functions into separate modules that can be easily tested by using simple Jest unit tests. The thing is, when you join a large legacy project, in most cases the team won't be willing to do any radical change to existing code unless you really sell the idea that you can make everybody's life better by refactoring this piece of code and you can do it quickly enough.

I've found that writing end-to-end (e2e) "happy scenario" tests for the most critical scenarios is the most effective solution. E.g. you can write tests for cases when user successfully

  • logs in,
  • signs up,
  • restores password.

My preferred tool for e2e testing is Cypress.

UPD. Recently I've tried "@testing-library/react"+"@testing-library/user-event" and it turned out not so bad! Don't have resources to write about it now, but hopefully will make another post about React unit tests in the nearest future. TLDR is I would replace some bits of Cypress with unit tests with these libraries.

Cypress

Cypress has well maintained documentation, they even have a section about best practices which I highly recommend to read. I would just like to mention several things.

Selecting elements for test
You can use data-qa attributes in your HTML and select elements in test like this:

cy.get('[qa="company-name"]').type('test-company');
Enter fullscreen mode Exit fullscreen mode

Long tests issues

Cypress doesn't like tests that take too much time to run. If a test takes too much time, Cypress can throw one of the following errors (there are GitHub issues about that - first, second). I've mentioned some ways to deal with it in this post.

Wait

There is only one situation I can imagine where you might want to write something like

wait(100);
Enter fullscreen mode Exit fullscreen mode

You are probably in a hurry and you plan to run Cypress tests only locally. Such tests will be very flaky if you run them as a part of CI/CD pipeline!

Waiting for a HTTP request to finish is covered well enough in the docs. Let's look at a couple of other useful approaches that I've also been using.

First of all, you can ask Cypress to wait until some thing is visible:

cy.get('[data-qa="sign-out-btn"]').should("be.visible");
Enter fullscreen mode Exit fullscreen mode

Second of all, you can use constructions like this (in tests or cypress commands):

cy.request({ ... }).then((response) => {...});
cy.then(()=> { cy.request({ ... }).then((response) => {...});
Enter fullscreen mode Exit fullscreen mode

Localstorage and setting env variables

cy.request({...}).then((response) => {
  window.localStorage.setItem('token', response.body.token);
  Cypress.env('userId', response.body.data.id);
});
Enter fullscreen mode Exit fullscreen mode

How to reset database after tests

If you have mongo, I have written a post about it.

If you have mysql - this might help. I haven't tried it, so not a legal advice! :)

Top comments (0)