Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Testing

GDamaso edited this page Jun 5, 2024 · 10 revisions

Testing

We have evaluated several testing frameworks and have decided to use different testing framework for both FrontEnd and Backend. FrontEnd uses Vitest and Backend uses Jest. More information about our Architecture Decision will be displayed in Architectural Decision Log

FrontEnd

FrontEnd uses Vitest as its testing framework due to it being a Vite-Native testing Framework. It is a testing framework that is based on Jest but it reuses Vite's Config and plugins.

Why use it?

  • Vitest uses Vite's conig and plugins, making it consistent across the app.
  • It is Jest Compatible
  • It reruns the related changes that you did. For example, changes on a component only triggers the test that is coupled with.
  • Out-of-box ESM, TypeScript and JSX support powered by esbuild.

Installation and Startup

Go to project directory

    cd /path/of/directory

Install Vitest

    npm install -D vitest

Add Vitest in your scripts

    test: Vitest

Create a vitest.config.ts in the root directory of your application.

    import { defineConfig } from 'vitest/config';
    import react from '@vitejs/plugin-react';

    export default defineConfig({
    plugins: [react()],
    test: {
        globals: true,
        environment: 'jsdom',
        setupFiles: './src/tests/setupTests.js',
    },
    });

environment: 'jsdom' simulate a browser-like environment in Node.js using jsdom. This is particularly useful for testing web applications as it provides a DOM implementation. setupFiles: './src/tests/setupTests.js' points to a JavaScript file that contains setup code for your tests. Such as configuring global variables, setting up mock functions, or initializing libraries that are required for your test.

Usage and Example

Testing Health Check Component if it displays NMP API is Healthy and Ready!

HealthCheck.ts

    import { useEffect, useState } from 'react';
    import axios from 'axios';

    const HealthCheck = () => {
    const [status, setStatus] = useState(null);
    useEffect(() => {
        const backEndUrl = import.meta.env.VITE_BACKEND_URL;
        const url = `${backEndUrl}/health`;

        axios
        .get(url)
        .then((response) => {
            console.log('Health Check data:', response.data);
            setStatus(response.data);
        })
        .catch((error) => {
            console.error('Error fetching data:', error);
        });
    }, []);

    return (
        <div>
        <h1>Health Check</h1>
        <h2>{status}</h2>
        </div>
    );
    };

    export default HealthCheck;

HealthCheck.test.ts

    import { vi, describe, expect, it } from 'vitest';
    import { render, screen, waitFor } from '@testing-library/react';
    import axios from 'axios';
    import HealthCheck from '../components/HealthCheck';

    vi.mock('axios');

    describe('HealthCheck Component', () => {
    it('fetches and displays HealthCheck Component response data', async () => {
        // Mocked data returned by the Axios Get request
        const response = 'NMP API is healthy and ready!';

        // Setup the mock to return the mocked data
        vi.mocked(axios.get).mockResolvedValueOnce({ data: response });
        render(<HealthCheck />);
        // Wait for the healthcheck data to be fetched and displayed
        await waitFor(() => {
        expect(screen.getByText('NMP API is healthy and ready!')).toBeInTheDocument();
        });
    });
    });

Backend

The Backend uses Jest, which seemed like the most popular testing solution for js/ts testing, which means there is a ton of community articles in support of it's usage and implementation. Since this is new to us, this seemed like as good of a choice as we could make.

Why use it?

Jest will be used for testing all of our endpoints, reinsuring proper implementation. When relying only in manual testing, one might be focused on testing parts of the application while neglecting others. With automated testing, we can ensure every commit checks all the backend endpoints, in a granular level, avoiding pushing bugs. It does add extra work for development, but this is a learning experience and the more tools we learn the better. Also, it might save time from having to go back and find bugs that could be caught with tests. It will also improve overall quality.

Jest is one of the most popular testing suites for TypeScript and we it seemed to to what we needed in straightforward way.

If you want to know more about Jest, here's a link on how to get started:

https://jestjs.io/docs/getting-started

Usage and Example

This is our simplest test, covering our /health endpoint:

/backend/src/test/health.test.ts

import testRequest from './testRequest';

describe('Test the health path', () => {
  test('returns status code 200 if healthy', async () => {
    const res = await testRequest.get('/health');

    expect(res.statusCode).toEqual(200);
    expect(res.text).toBe('BetterBerries API is healthy and ready!');
  });
});

Note the test should cover both the status and any data contained in the response. This ensures we're not giving false positives back to requests.

You can execute the test script, running all tests in the backend/src/test directory, with npm run test.

Husky

We decided to implement Husky for testing, formatting and linting execution upon git hooks. Since it's a pretty thorough test and takes a minute, it's currently triggered by a push hook.

Our repo root is not a node project, so we had to install husky in either frontend or backend. We chose the backend. It switches between the backend and frontend folders to execute the required scripts.

cd frontend
npm run test
npm run format
npm run lint

cd ../backend
npm run test
npm run format
npm run lint

This is really cool because we can ensure our code is always formatted, linted and tested before we push it to our remote branches.

Husky Pre-push execution Husky Prepush execution

Husky failed execution Husky Prepush execution failing