Frontend testing

Rajan Lagah
5 min readJun 3, 2024

--

( Everything is in context of ReactJS, next.js or react based technologies )

Types of testing

  1. Unit testing: For each component write every possible state and then write test to verify it. For example your custom input field will have these and more unit text cases.

2. Integration Testing: You will test bunch of components that are dependent or used together in your website. Example Login form will have

  • text input
  • Select tag ( for selecting gender )
  • Button

and these will work together to create Form. Now you will write test for every possible state of the form.

3. E2E testing: Testing journeys like login or onboarding. E2E verify that the application’s components and dependencies, such as databases, networks, and external services, work together correctly.

4. Visual testing: Detect visual changes and test if its consistent. Tools like Applitools help you detect visual difference bw multiple branches

Why is visual testing important?

Now look, Our tests will pass even if the UI is broken. If we had tests for, if text is rendered or heart icon is not clicked etc, they will pass but still if we see for human its not readable or usable. So to test the visual appearance we do visual testing.

From where should we start?

In my opinion we first guarantee core business logic. For every change that is deployed we should run test upon our core feature and they should we working e2e. It should be the starting point and bonus is e2e testing take least amount of time as compare to other unit and integration testing.

Getting started with E2E

Cypress is very mature tool that we can use to test our web apps. It offers great community and features like

  • Same syntax that is used in chai/mocha etc.
  • Cross browser testing
  • timeline feature same like Redux that help debug your test cases
  • Easy integration with visual testing tools.

After installing cypress we generally use these 2 commands the most.

  1. To run test in browser
npx cypress open

2. To run tests in CI/CD.

npx cypress run

Basic functions

  1. cy.visit(url): Visit cmd will tell cypress to open specific page for which we want to add test cases.
  2. cy.get(): Get cmd will return component of your website. For example
cy.get('.input') // will return html element/s with class input
cy.get('#submit-btn') // will return html element with class submit btn
cy.get('[data-testid="login-heading"]') // will return html element with attribute data-testid as login-heading

using class name or id is bad practice for testing. Instead standard practice is to use data-testid attribute.

3. should: assertion command used to assert that a specified condition or state is met.

cy.get('#submit-button').should('be.visible');
cy.get('#submit-button').should('disabled');

4. click: CMD to click HTML

cy.get('#submit-button').should('exist').click()

5. type: It mimic user typing.

cy.get('[data-testid="email-input"]').should('exist').type('rajanlagah@gmail.com')

6. intercept: This cmd will intercept API call going out from FE and return the desired response.

describe('Intercepting GET Requests', () => {
it('should intercept and mock a GET request', () => {
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: { users: [{ id: 1, name: 'John Doe' }] },
}).as('getUsers');
    cy.visit('/users');    cy.wait('@getUsers').its('response.body').should('have.property', 'users');
cy.get('.user').should('contain', 'John Doe');
});
});

Advance concepts

  1. Fixtures: So you want to store response outside the test file to maintain it properly. Cypress has got your back. Fixtures are used to store json
// cypress/fixtures/newUser.json
{
"id": 3,
"name": "Alice Johnson"
}
// cypress/integration/post_request_with_fixture_spec.js
describe('Intercepting POST Requests with Fixtures', () => {
it('should intercept and mock a POST request using fixture data', () => {
cy.intercept('POST', '/api/users', { fixture: 'newUser.json' }).as('createUser');
cy.visit('/create-user');
cy.get('input[name="name"]').type('Alice Johnson');
cy.get('button[type="submit"]').click();
cy.wait('@createUser').its('response.body').should('include', { name: 'Alice Johnson' });
cy.get('.user').should('contain', 'Alice Johnson');
});
});

2. Custom cmds: Tired of writing same long cmds again and again? You can overwrite the cmds as per your convince. For example lets overwrite ‘get’ cmds

Cypress.Commands.overwriteQuery('get', function newGet(originalFn, ...args) {
let selector = args[0]
if (typeof selector === 'string' && selector.startsWith('$')) {
selector = `[data-testid="${selector.substring(1)}"]`;
}
args[0] = selector
const innerGet = originalFn.apply(this, args)

return (subject) => {
return innerGet(subject)
}
})

now instead of doing cy.get(‘[data-testid:”submit-btn”]’) we can do shortcut cy.get(‘$submit-btn’). Our logic will replace $ with required thing and format it accordingly.

3. Accessibility testing: Want to test if your website is ADA compliances compatible? You can use tools like cypress-axe to easily test these things.

Naming conventions

describe('login page')

describe is used to setup the context so make sure to pass the correct context.

it('should logout and render login page')
it('show error on invalid input')

describe will have multiple it and you should have action -> reaction specified in your it block.

data-testid="login-page__submit-btn--primary"

We can use BEM to name all the test ids.

B -> Block: Here login page is higher-level component.

E -> Element: Submit btn is the element

M -> Modifier: Current state of button/version of the element.

Conclusion

Cypress testing is fun and time consuming. No doubt that it will streamline the process of deployments and provides you with confidence for deploying your websites. But if you are running out of resources then consider adding tests to at-least your core functionality. And when your write test dont forget to fail that test first by messing up with data-testid or state of element, this will ensure that your test will actually break when its required to.

--

--