Looking for a specific post? Checkout the Blog Index.

By Kimserey Lam with

Frontend Testing Fundamentals With Cypress

Mar 20th, 2020 - written by Kimserey with .

Cypress is a end to end frontend testing tool which simulates user navigation while running in browser. Few weeks ago we discovered the fundamentals of it, how we could install it and how we could create custom commands for it. The trick with testing is to find the right balance between not enough tests and too many tests. Not enough tests would yield less confidence in code changes while too many tests will yield more maintainance hassle. Therefore what we really want is to focus on specific aspects of our application that if properly tested will ensure that the quality remains. Today we will look at three of the main pillars, the routes, the posts and the displays.

Testing Routes

The first pillar of testing is ensuring that all pages are accessible from their direct route. When manually testing, we would go over each possible pages and visit them via the URL, extremely time consuming. We can use Cypress to automate that test using a combination of the following commands:

  • visit to visit a direct URL,
  • get to retrieve an element on the page - assiociated with a landmark item on the page,
  • should will make an assertion.

For example we would mark a title on the page as landmark:

1
<h1 data-cy="landmark">Landing page</h1>

And we would be able to assert that navigating to the route works by doing the following:

1
2
3
4
5
6
7
context('Routes', () => {
  it('navigates to /', () => {
    cy.visit('/')
      .get('[data-cy=landmark]')
      .should('contain', 'Landing page');
  });
});

We visit the URL, get the landmark and assert its content. If we have more pages, for example /form and /message, we can add more tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
context('Routes', () => {
  it('navigates to /', () => {
    cy.visit('/')
      .get('[data-cy=landmark]')
      .should('contain', 'Landing page');
  });
  
  it('navigates to /form', () => {
    cy.visit('/form')
      .get('[data-cy=landmark]')
      .should('contain', 'Form page');
  });

  it('navigates to /message', () => {
    cy.server()
      .route('/api/data', 'some message')
      .visit('/message')
      .get('[data-cy=landmark]')
      .should('contain', 'Message page');
  });
});

The last /message URL example showcase how we can stub a route to our API before visiting a page, here the /message page loads GET /api/data to retrieve a message, if we didn’t stub the route, the page will fail loading.

Using this example of visit, get, should we are able to ensure that our website loads properly every pages.

Testing Forms

The second pillar of testing is ensuring that the forms posted post the data that we expect. Posting a form involves more user actions which Cypress helps us to simulate. To perform our test we use the following commands:

  • server to start a mock server,
  • route as to stub the route and label the route,
  • type to type in an input field,
  • submit to submit the form,
  • wait its to wait for the route to be invoked and assess the content body of the request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
context('When submitting form', () => {
  it('sends the proper content', () => {
    const fixture = {
      name: 'name',
      lastname: 'lastname'
    };

    cy.server()
      .route({
        method: 'POST',
        url: '/api/data',
        status: 204,
        response: ''
      })
      .as('post')
      .visit('/form');

    cy.get('[data-cy=name]').type(fixture.name);

    cy.get('[data-cy=lastname]').type(fixture.lastname);

    cy.get('form').submit();

    cy.wait('@post')
      .its('request.body')
      .should('include', fixture);
  });
});

In this example we use mock the POST /api/data endpoint and alias it with .as('post') in order to refer to it once we submit the form.

We find each field of the form like we did for the routes test and use .type(...) to type a value into the input simulating user typing data.

At the end of the test we submit the form and use .wait('@post') to wait for the POST route to be invoked and assess its body with .its('request.body').should('include', ...).

.wait() is a powerful command which allows us to wait for the route to be called and provide us access to the response body for assertion. The other important point is that .wait() indicates yo Cypress that all commands should be paused while the alias is awaiting resolution.

Asserting that we post data the way we expect it is extemely important as it ensure that we don’t send the wrong field in the wrong place, a typical example is to mix firstname and and username fields, if we were to send that, our backend wouldn’t be able to catch it therefore we ensure that the fields are correct from the user typing himself simulated by Cypress.

Testing Displays

Lastly the third pillar of testing is the display testing where we ensure that data returned by the server displays in the content we expect them to do so. Very similar to routes test, we can add data-cy attribute on particular HTML blocks which indicate success of display.

1
<div class="alert alert-info" data-cy="message"></div>

assuming that this div will be populated by the call to GET /api/data, and we write the following test:

1
2
3
4
5
6
7
8
9
10
11
context('When visiting message', () => {
  it('displays the proper content', () => {
    const fixture = 'some value';

    cy.server()
      .route('/api/data', { message: fixture })
      .visit('/message')
      .get('[data-cy=message]')
      .should('contain', fixture);
  });
});

We stub the route and assert that once the page load, the message is visible from the HTML block.

And that concludes today’s post!

Conclusion

Today we looked at the three fundamental pillars of frontend application testing. We started by looking at testing accessibility of routes, we then moved on to test forms and we completed the post by looking at testing displays. With this tests, we looked into different commands of Cypress which would allow us to stub routes, wait for responses, visit specific URLs of our application and assert on data. I hope you liked this post and I see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.