UI інтеграційні тести для React аплікації на Java

💡 Усі статті, обговорення, новини про тестування — в одному місці. Приєднуйтесь до QA спільноти!

Привіт мене звати Олександр і час від часу я пишу статті про автоматизоване тестування на джаві. Працюю на посаді Lead Software Test Automation Engineer.

Однією з найбільших проблем автоматизованого тестування є надійність тестів, а саме кількість false positive результатів. Однією з умов надійних тестів є їх ізоляція. Логічним продовженням ідеї ізоляції є перехід на сходинку нижче у тестовій піраміді і використання інтеграційних тестів.

Такі тести мають ряд переваг таких як надійність та швидкість виконання. Проте мають і декілька недоліків, головними з яких є значна складність підтримки mocked даних (дані, які будуть використані аплікацією замість даних від сторонніх сервісів), а також неможливість виявити деякі дефекти.

В мережі є багато матеріалів по E2E-тестуванню і значно менше про інтеграційне тестування. Через це, на мою думку, стаття буде цікавою широкій аудиторії. У ній продемостровано підхід до побудови інтеграційних тестів для простої React аплікації, яка відображає цікавий факт про котів. Сам факт отримується зі стороннього сервісу.

Тест з котом

import { useEffect, useState } from "react";
 
function App() {
  const [fact, setFact] = useState("");
 
  useEffect(() => {
    fetch(process.env.REACT_APP_CAT_FACT_URL)
      .then((response) => response.json())
      .then((data) => setFact(data.fact))
      .catch((error) => console.log(error));
  }, []);
 
  return (
    <div className="App">
      <h1>Cat Fact</h1>
      <p data-test-id="fact-text">{fact}</p>
    </div>
  );
}
 
export default App;

Ця аплікація має два режими роботи, а саме робота з реальним сервісом (https://catfact.ninja/fact) та робота з mock-сервісом (http://localhost:8080/fact).

В тесті я планую перевірити, що користувач має можливість бачити факт про кота. І з цим є дві проблеми. По-перше, аплікація залежна від стороннього сервісу, який нам би не хотілось тестувати, бо ми ним не володіємо.

По-друге, через те, що факт генерується випадковим чином, наш тест стає або занадто абстрактним і ми не перевіряємо конкретний факт, або нестабільним, тому що факт кожен раз змінюється. Обидві ці проблеми вирішуються за допомогою підставляння наших даних замість реальних даних від сервісу.

Фреймворк

Тестовий фреймворк складається з Maven, Lombok, TestNG, Wirework, Cucumber та Selenide. Також використана класична layered-архітектура.

Тестовий фреймворк зберігається у тому самому репозиторії, що і аплікація. Це зроблено для синхронізації версій тестів та аплікації.

@integration
Feature: Data presentation
 
  Scenario Outline: User is able to see cat fact
    When [Sys] Cat fact service is mocked
    When [UI] User opens main page
    Then [UI] The cat fact <catFact> should be shown
 
    Examples: Examples:
      | catFact                                                              |
      | Approximately 40,000 people are bitten by cats in the U.S. annually. |

Єдина відміність від E2E-тестів в додатковому кроці, в якому прописується, як mock-сервіс має відповідати на запит аплікації.

@When("[Sys] Cat fact service is mocked")
public void catFactServiceIsMocked() throws IOException {
    String json = new String(Files.readAllBytes(Paths.get(mockJsonLocation() + "/catFact.json")));
 
    stubFor(get(urlEqualTo("/fact"))
            .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Access-Control-Allow-Origin", "*")
                    .withBody(json)));
 
}

Для розгортання аплікації можна використати наступні команди:

npm run start:development // external service is mocked
npm run start:production // external service is NOT mocked

Для локального тестування та для розробки тестів можна використати Mockoon, як mock-сервер.

При виконанні інтеграційних тестів Mockoon не потрібен, тому що Wiremock сервер підіймається в методі beforeAll.

public class MockHooks {
 
    private final static WireMockServer wireMockServer = new WireMockServer();
 
    @BeforeAll
    public static void startWiremockServer() {
        wireMockServer.start();
    }
 
    @AfterAll
    public static void stopWiremockServer() {
        wireMockServer.stop();
    }
}

Jenkins pipeline складається з чотирьох кроків, а саме:

  • клонування репозиторію;
  • розгортання аплікації у dev моді;
  • виконання інтеграційний тестів;
  • генерація репорту.

pipeline {
    agent any
 
    stages {
        stage('Clone repo') {
            steps {
                git credentialsId: 'git', branch: 'main', url: 'https://gitlab.com/OleksandrPodoliako/cat-fact.git'
            }
        }
 
        stage('Build and start app') {
            steps {
                dir('app') {
                    bat 'npm install'
                    bat 'start /B cmd /C "npm run start:development | findstr /C:\"Compiled successfully!\""'
                }
            }
        }
 
        stage('Run integration tests') {
            steps {
                dir('integration-tests') {
                    bat 'mvn clean test -Pui-integration'
                }
            }
        }
 
        stage('Generate report') {
            steps {
                dir('integration-tests') {
                    publishHTML([allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'target/cucumber-reports', reportFiles: 'cucumber-report.html', reportName: 'Cucumber Report'])
                }
            }
        }
    }
}

Повний код фреймворку можна знайти за посиланням.

👍ПодобаєтьсяСподобалось1
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Якщо б кожен продукт був би такий простий для тестування, то в нас би не було роботи
Це можливо досягти лиш коли фронтедщікі адаптували запуск апки на локалхост та на тому ж агенті запускати тести

Безумовно це гарний підхід і так робити треба, але чи не було б більше сенсу якщо б ви робили такі тести в фронтенд репозиторію на їх же стеку?

Це просто концепт і звичайно він спрощений.

Я би не сказав що прям більше сенсу тому що кожен підход має свої плюси і мінуси, але без критичних проблем чи лімітацій.

Приклади гарні, але React в заголовку і тегах зайвий, імхо. Тут же нема різниці шо тестувати.

Я рахую що все таки варто вказувати тип аплікації тому що інтеграційні тести сильно звязані з аплікацією і їх важко розглядати окремо від аплікації.

Якби те саме було зроблено на Vue чи без бібліотек взагалі, хіба б змінилося що в тестах?

Підписатись на коментарі