DEV Community

Robert Kregloh
Robert Kregloh

Posted on

What is integration testing in UI/Front End?

TLDR

  • Jest integration tests are cheaper/faster to write than cypress integration tests.
  • Cypress tests are simpler to write for interactions like HTML Canvas and API calls.
  • A combination of both is ideal.

What is integration testing in UI/Front End?

"What is integration testing?" seems to be a question I find that the answer differs depending on who you ask. You would get a different answer from a QA Engineer than a UI Engineer. A Backend Engineer might have a third answer. The theme that all three share is you are testing to ensure various parts of an app work together as intended.

For fun, I asked ChatGPT what Integration testing was and this is the summary that it came back with...

ChatGPT Response To Prompt what is integration testing

Integration testing helps ensure that the software functions as a cohesive unit, and it plays a crucial role in identifying issues related to data flow, control flow, and interface communication between components. It provides confidence that the integrated components work together correctly and that the overall system meets its specified requirements.

What should you focus on for an integration test?

In a React app, you will have various components of different size and scale. But they will all work together to bring your application to life. So with these various components, you have the option of testing them at a unit and integration level.

For integration testing, you focus on the interactions between your components keeping in mind how a user would interact with your app. As discussed above, you want to test the behavior of all moving parts working together. Let's take a todo list for example. You can assume it looks like the mockup below.

Image description

What are the components here?

If you were to build out this mock, you would probably have a structure like the following.

  • ListItem - A component for the list item
  • ListItemAction - A component for the action (probably overkill but lets run with this for the example)
  • ListGroup - A component for the group of list items
  • TodoListView - A view (or page) component where this feature will be displayed.

So how would you write an integration test for this application?

In our case, all 4 components will share interactions at some point. Consider what the interactions a user will be performing when using our app.

  1. Adding a list item.
  2. Removing an existing list item.
  3. Updating a list item.

When performing any of the actions above you interact with every component. Those components integrate together for our app to complete it's function. So how do you test this?

You start by looking at your View component, the top most level of your tree. It houses the highest level parent of your component structure. You write tests in your View component that check the following.

  1. It adds an item when the user types in the bocks and clicks the plus action.
  2. It removes the correct item when a user clicks on the x action on an existing item.
  3. It updates the correct item when the user changes the text in a line item and clicks on the checkmark action.

These are all simple tests. But, they ensure interactions and component logic execute properly. In essence, you are integrating those components into the View component. You are testing their integration.

But don't unit tests double as integration tests sometimes?

Sometimes, when writing unit tests you tend to get integration test coverage for free. (to a certain point). There is a line between integration and Unit tests that tends to blur. In the case of this app, most of the unit tests you might write end up covering some level of integration.

For example, testing the add action on the ListGroup component relies on 2 other components to succeed. If your test passes, you are testing the integration of those two components into ListGroup. You are also writing a unit test for ListGroup. It's a test to see the new item is added correctly.

What tools are available?

For the purpose of this blog post, I am going to focus on two tools in particular. Jest/ReactTestingLibrary and Cypress and go over what their strengths and weaknesses are.

Let's start with Jest

Jest has long been my favorite got to test runner when it comes to UI unit and integration testing. With tools like React Testing Library (Testing Library framework for React) you get the benefit of very easy to write and maintain tests, as well as a quick and efficient way to run our tests.

Jest allows you to write your tests in your app codebase and execute them in your console with a couple of simple commands (an example would be something like yarn run jest --watch *filename*). You get real time feedback of your tests passing or failing. It allows you to iterate very quickly through your test writing process. They are also easy to integrate into your pipelines and execute when new code is added.

Cypress

Cypress is a tool I started working with recently and while very powerful, it also has some limitations. Let's first talk about the strength in my opinion of Cypress. Cypress is great for writing automation tests for UI and End To End tests. I find that in my experience, writing Cypress tests is rather easy when you have the proper data setup and configuration. If these are missing, writing tests could prove very costly and time consuming.

Cypress has the benefits of allowing you to intercept api calls and interact with the dom in a more visual way than a Jest test, thus feeling more like an actual user journey through your app. You also have the option of running Cypress in a UI or in a headless mode. However some things to note about cypress tests. They tend to be more expensive to write and take longer to execute on when compared to than Jest. In addition, depending on your setup, you might also have a separation of code. For example, your Cypress might live in an external repository. If you need to do things like add selectors or fix a bug while you are writing your test, your workload has suddenly gotten bigger.

So which would you choose?

In my personal opinion, I would reach for Jest when it comes to unit and integration testing as opposed to Cypress as my go to. However, there are some cases where Jest just wont cut it. An example I can give you is working with certain elements like HTML Canvas. I have found that in these cases, Cypress just feels more natural and easy to work with (assuming your data and test environment are set up correctly).

Cypress is also a great way to validate that your API calls are reaching the backend as expected. In Jest you make the assumption that the calls are working, and to be fair, it is a somewhat (don't cancel me for saying this...) good assumption to make. Unless there are some issues with the API itself, you can confirm that your calls are being made with the correct data. If your backend is also writing tests, they will make sure that they accept the correct data. This is where documentation is important.

Let me be clear.

I am not suggesting that you don't need to test your API calls. I am suggesting that as long as you have a few Cypress tests (or some other method of testing) that DO test the calls as part of an automation test coverage plan, mocking your calls and responses in Jest is perfectly acceptable. Beneficial even!

Enough Talk, Lets write an integration test!

I find that sometimes showing the code helps get the point across. For this example, I am going to use a simple button counter.

ButtonCounter.js

import React, { useState } from 'react';

const ButtonCounter = (count, onIncrement) => {

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => onIncrement()}>Increment</button>
    </>
  );
};

export default ButtonCounter;
Enter fullscreen mode Exit fullscreen mode

In the code above, when you click on the button, a few things happen. The user clicks on the button and it fires an event. This event will then update the state of the count by setting the new value to count + 1 resulting in an increment of 1 every time.

App.js

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState(0);

  const handleIncreaseCount = () => {
    setCount(count + 1);
  };

  return (
    <>
      <ButtonCounter count={count} onIncrement={handleIncreaseCount}/>
      { count >== 2 ? (<p>Count Is more than or equal to 2</p>) : null }
    </>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

App.test.js

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import App from './App';

it('increases the current count by 1 when clicking on the increment button.', () => {

  const { getByText } = render(<App />);

  const currentCount = getByText('Count: 0');
  expect(currentCount).toBeInTheDocument();

  const increaseCountByOne = getByText('Increment');
  fireEvent.click(incrementButton);

  expect(currentCount).toBe('Count: 1');
});

it('shows the count is greater than or equal to 2 text when count passes the value of 1.', () => {

  const { getByText } = render(<App />);

  const currentCount = getByText('Count: 0');
  expect(currentCount).toBeInTheDocument();

  const increaseCountByOne = getByText('Increment');
  fireEvent.click(incrementButton);
  fireEvent.click(incrementButton);

  const countGreaterThanText = getByText('Count Is more than or equal to 2');

  expect(countGreaterThanText).toBeVisible();
});


Enter fullscreen mode Exit fullscreen mode

This is a very simple example, but the principal for an integration test is easy to follow. In the code above, you are running a test to check when a user clicks on the count button, a callback is fired to the parent (which is App in our case) and the state is then updated. You are testing the integration of the callback with the App count state.

You are also testing a value is displayed as a result of clicking the button multiple times. You are using one component to cause a change in another and testing the props passed to it from the parent behave as expected. You test their integration.

Please keep in mind this is a very simple example and not a real world scenario, but I hope this gets the point across.

To wrap it up

Integration testing in the context of UI/Front End development is an important aspect of ensuring that various components of an application work together as intended. The perspectives of QA Engineers, UI Engineers, and Backend Engineers may differ, but the common thought is the verification that different parts of the app are integrated correctly and function correctly together.

UI integration tests, especially in a framework like React, focus on testing interactions between components. Using the example of a to-do list application, integration tests would ensure that components like ListItem, ListItemAction, ListGroup, and TodoListView work seamlessly when a user performs actions like adding, removing, or updating a list item.

Jest offers a straightforward and efficient way to write and execute UI integration tests. Jest is easy to integrate into CI/CD pipelines making it a preferred choice for many developers. On the other hand, Cypress, while powerful for integration and End-to-End tests, might be more suitable for specific scenarios, such as interactions with elements like HTML Canvas.

Ultimately, the choice between Jest and Cypress depends on the nature of the application and the testing needs.

In practice, achieving a balance between unit and integration tests is essential. While unit tests may inadvertently cover some integration aspects, dedicated integration tests help ensure that components work seamlessly together.

Top comments (1)

Collapse
 
apssouza22 profile image
Alexsandro Souza

I loved this post. I really think that E2E UI tests should be used conciously to not say avoided.