Skip to content

Why Write Tests for Front-End Applications ?

Writing tests for front-end applications is an essential practice in Hubtel that helps ensure the reliability, stability, and performance of your applications. For front-end applications, testing provides a safety net that helps developers catch bugs early, improve code quality, and facilitate maintenance. It also supports agile development practices by enabling refactoring and feature addition with confidence, knowing that your changes haven't broken existing functionality.

As at now, Vitest has been adopted as the choice framework for writing tests in Frontend applications in Hubtel. It stands out as a modern, Vite-native testing framework that brings speed and efficiency to the testing process. Also, its compatibility with libraries used in both Nuxt.js (Vue) and Next.js (React) applications positions it as a versatile choice for developers working across the Vue and React ecosystems.

Getting Started

To set up a testing environment for your frontend application using Vitest, follow the steps below. This setup includes configuring scripts in package.json, creating a Vitest configuration file, and setting up a sonar-project.properties file for SonarQube integration.

Step 1: Update package.json

json
"scripts": {
  "coverage": "vitest run --coverage",
  "test": "vitest",
  "test-ui": "npx vitest --ui",
  // Make sure to include your other script entries as well
},

Step 2: Create vitest.config.mts in the src folder

For Nuxt.Js configure your vitest.config.mts as follows

typescript
import { defineConfig } from "vitest/config";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import path from "path";

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: [
        "vue",
        {
          "@vueuse/head": ["useHead"],
          "#app": ["useNuxtApp", "useRouter", "useRoute"],
        },
      ],
    }),
    Components(),
  ],

  test: {
    globals: true,
    environment: "jsdom",
    reporters: [["junit", { outputFile: "test-results.xml" }]],
    coverage: {
      reportsDirectory: "./coverage",
      provider: "v8",
      reporter: ["text", "html", "lcov", "cobertura"],
    },
  },

  resolve: {
    alias: {
      "@": path.resolve(__dirname, "."),
      "~": path.resolve(__dirname, "."),
      "#app": path.resolve(__dirname, "node_modules/nuxt/dist/app"),
      "@vueuse/head": path.resolve(
        __dirname,
        "node_modules/@unhead/vue/dist/index"
      ),
    },
  },
});

For Next.Js configure your vitest.config.mts as follows

typescript
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],

  test: {
    globals: true,
    environment: "jsdom",
    reporters: [["junit", { outputFile: "test-results.xml" }]],
    coverage: {
      reportsDirectory: "./coverage",
      provider: "v8",
      reporter: ["text", "html", "lcov", "cobertura"],
    },
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "."),
      $components: path.resolve(__dirname, "../../components"),
      "~": path.resolve(__dirname, "."),
    },
  },
});

Step 3: Create sonar-project.properties in the src folder

properties
# sonar-project.properties
sonar.projectKey={sonar_project_key}

sonar.sources=src
sonar.tests=tests
sonar.language=ts
sonar.testExecutionReportPaths=test-results.xml
sonar.typescript.lcov.reportPaths=coverage/lcov.info

You can find the sonar_project_key for your project on sonar.hubtel.com using the steps below:

  • Visit sonar.hubtel.com and search for your project in the search input.
  • Click to view details of your project. Click on project information on the top right corner of the details page
  • Copy the project key under project information and paste as value for sonar.projectKey in the sonar-project.properties file

Step 4: Add Development Dependencies

For Nuxt.Js configure your dev dependencies as follows:

json
"dependencies": {
  "@vitest/coverage-v8": "^1.3.1",
  // Include your other dependencies as well
}
"devDependencies": {
  "@vue/test-utils": "^2.4.4",
  "jsdom": "24.0.0",
  "unplugin-auto-import": "0.17.5",
  "unplugin-vue-components": "0.26.0",
  "@vitest/ui": "^1.3.1",
  "vitest": "^1.3.1",
  // Include your other devDependencies as well
}

For Next.Js configure your dev dependencies as follows:

json
"dependencies": {
  "@vitest/coverage-v8": "^1.3.1",
  // Include your other dependencies as well
}
"devDependencies": {
  "jsdom": "24.0.0",
  "@vitest/ui": "^1.3.1",
  "@vitejs/plugin-react":"^4.2.1",
  "@testing-library/react":"^15.0.2",
  "vitest": "^1.3.1",
  // Include your other devDependencies as well...
}

After making these changes, don't forget to run npm install, yarn install or pnpm install to install the newly added packages.

Testing Guidelines

1. Test Naming Convention

  • Descriptive Naming: Use descriptive and clear names for your tests that reflect the UI component or interaction being tested. For instance, testButtonColorChangesOnClick() or testFormValidationFailsWithEmptyInput() clearly describes what the test will verify.

2. Test Organization

  • Logical Grouping: Organize your frontend tests into logical groups or classes based on the component or feature they test. This organization helps in maintaining a clean structure and makes it easier to locate and run tests related to specific components of the user interface.

3. Test Coverage

  • Scenario-Based Coverage: Ensure comprehensive test coverage that includes various user interaction scenarios. This should include both common use cases and edge cases, such as clicking, dragging, entering text, handling pop-ups, and more.

  • Positive and Negative Cases: Cover both successful interactions and error conditions to ensure the UI handles all states gracefully.

4. Arrange, Act, Assert (AAA)

Structure your frontend tests using the Arrange, Act, Assert pattern

  • Arrange: Set up the UI state. This may involve rendering a component with specific props or setting up a mock environment.
  • Act: Simulate user interactions such as clicks, keystrokes, or mouse movements.
  • Assert: Verify that the UI responds as expected, such as changes in the DOM, updates to component state, or callbacks being invoked.

5. Isolation

  • Component Isolation: Render components in isolation. This helps test components in a clean room environment without side effects from other parts of the application.
  • Mock Dependencies: Mock external dependencies such as APIs, other components, or services to ensure tests are focused solely on the component behavior.

6. Readable Assertions

  • Clear Assertions: Utilize assertion libraries that are expressive and provide clear feedback. For example, assertions that check specific changes in the DOM, verify displayed values, or check the existence of elements after interactions help clarify the purpose of the test.

7. Test Data

  • Relevant Data Scenarios: Use diverse and relevant test data reflecting different user input scenarios, including boundary cases and incorrect or unusual inputs to ensure robustness in user input handling.

8. Avoid Test Dependencies

  • Independent Tests: Design tests to be independent from one another to avoid cascading failures and ensure that the failure of one test doesn’t affect others. Each test should setup its own environment and clean up afterwards.

9. Keep Tests Fast and Deterministic

  • Efficiency and Reliability: Keep your frontend tests fast and free from external variability. Avoid relying on real network calls or slow operations. Use mock data and keep tests focused on small units of functionality to ensure they run quickly and produce consistent outcomes every time.