DEV Community

Bryce Dorn
Bryce Dorn

Posted on • Updated on

UI Testing Best Practices ๐Ÿ“œ

Here at Glassdoor we take testing seriously, but the main focus has been on end-to-end (E2E) integration tests as opposed to small, quick unit tests. I've been writing a decent amount of UI tests these past few weeks and thought I'd share a handful of patterns I've been adhering to - hopefully this can help guide good decision making when writing tests and make it easy to write more maintainable code.

That was all a test, Morty

The function names and examples I will provide are specific to Jest and RTL, but the concepts apply to other frontend testing libraries.

Know what not to test ๐Ÿง 

Yes, the most important concept I have to share is about not testing. This may not apply to all situations but at Glassdoor we have thorough E2E integration testing, and it's essential to understand the coverage that these tests provide and the use cases that should be covered by them, in lieu of a UI test.

Not every feature will require an integration test. If a use case requires ~3-4 mocks and the experience opens/closes modals and updates state, it should be left to integration testing. But when adding to or creating a new frontend component, a simple unit test should suffice.

โŒ Bad example for a unit test (E2E):

  • Ensuring user login (user input, response) works as expected and lets a user view an admin page.

โœ… Good examples:

  • Adding a new <option> to a <select> and verifying it's shown.
  • Adding a click event to a button and confirming it fires.

Use snapshots wisely ๐Ÿ“ธ

Use bombs wisely.

Thanks, Peppy.

Snapshot testing is a great way to keep track of unexpected changes to a component. But it should not be confused with an actual test.

The use case for snapshots is when making changes to a shared component, it will provide a list of components that are affected. But that's it! There is still manual effort required to confirm the change didn't break those components.

Make it readable ๐Ÿ“–

Tests, just like code, end up being compiled to a garbled mess of characters. It's the duty of the developer to write clean, clear code to convey an idea to both the computer that interprets and the other developers that read it.

Jest provides a very readable syntax for test documentation, so use it!

โŒ Bad:

describe('component', () => {
  it('performs correctly', () => {
Enter fullscreen mode Exit fullscreen mode

โœ… Good:

describe('the admin page', () => {
  describe('when a user is not logged in', () => {
    it('shows a login button', () => {
Enter fullscreen mode Exit fullscreen mode

Notice how the test output will read like a complete sentence - this is what you should always strive for. That way, if a test fails on commit or in CI, there's a clear reason for it.

Be concise & consistent ๐Ÿ”

Each test should be as small as possible. The same concepts apply to DRY principles; here are some examples of good patterns to follow:

  • If there are multiple tests that share the same logic, share it via beforeEach or afterEach.
    • If testing multiple aspects of one component, define the render once in beforeEach.
  • If there are values inside a component that are referenced in a test, pull them out into consts and import them in both the test and in the component.
    • For example, when checking internationalized strings, instead of hardcoding the English value you can instead reference the output of an i18n library for that key.
  • Prioritize using test IDs over matching raw text, in case that text ever changes. If your team has a different pattern than what RTL encourages (data-testid), specify this in your config.
    • At Glassdoor we use data-test
  • If the same mock is used in multiple tests, define the response outside of the test and reference it in both places.

Mock fetches ๐Ÿ”ฎ

For data-driven components, mocking an API response is easy and allows tests to mirror their use in production. Given the advent of hooks, it's now much easier to position a GET request next to the output of a component, and mocking this data is just as easy!

I've been using @react-mock/fetch which makes it super easy to mock any HTTP request that a component relies on. It's as simple as wrapping a component in a <FetchMock> and providing the response:

import { FetchMock } from '@react-mock/fetch';

const mockedResponse = {
  matcher: '/ay',
  method: 'GET',
  response: JSON.stringify({ body: 'yo' })

  <FetchMock options={mockedResponse}>
    <MyComponent />
Enter fullscreen mode Exit fullscreen mode

Depending on the use case, you may need to wrap the test in an act() or setImmediate() to proceed to the next iteration of the event loop and allow the component to render.

When to run tests ๐Ÿš€

The way we do it here at Glassdoor is in multiple stages:

  • Husky prepush hook before pushing to remote, as well as
  • A Jenkins merge build before merging a pull request into the target branch

It's up to your team & how you'd like to organize your CI, but you should be doing at least one of these to position your tests as a line-of-defense against breakages.

The end ๐Ÿ‘‹

That's all for now, go write some tests!

Top comments (4)

akirautio profile image
Aki Rautio

This is a very useful list of easily applicable but effective things that can keep the application working properly. :)

Snapshot testing is a great way to keep track of unexpected changes to a component. But it should not be confused with an actual test.

I'm very happy to see that some people get the idea of snapshot testing right because there is a lot of confusion about it.

hugoliconv profile image

I want to start adding test to our project but I don't know where to start, I was thinking to test using jest and enzyme, but I have heard good things about React Testing Library, what do you recommend me?

bryce profile image
Bryce Dorn

Hi there! If you're using React, I'd recommend RTL with Jest. Here's a good tutorial that walks through how to get started:

michaelvisualai profile image
Michael Battat

Great post, Bryce! Very interesting to see how you do UI testing and the methods used. Looking forward to reading more - and I've got some related content as well in case you're interested.