go back

UI Testing Best Practices 📜

December 11, 2019

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):

✅ Good examples:


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:


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' })
};

render(
  <FetchMock options={mockedResponse}>
    <MyComponent />
  </FetchMock>
);
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:

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!

go back