Testowanie modułów Node.js z użyciem Chai, Sinon i Mocha

Grzegorz Wziątek

Zaktualizowaliśmy ten tekst dla Ciebie!
Data aktualizacji: 26.12.2024
Autor aktualizacji: Adam Olszewski

Wprowadzenie

W tym artykule pokażę, jak pisać testy jednostkowe dla modułów Node.js i ich zależności. Testowanie jednostkowe jest kluczowe, aby małe zmiany w kodzie nie zepsuły aplikacji. Błąd w kluczowym systemie może prowadzić do poważnych konsekwencji, jak w przypadku awarii systemu bagażowego na lotnisku, co może skutkować niezadowoleniem klientów, pozwami i karami finansowymi. Pisanie efektywnych testów jednostkowych, testów integracyjnych i testów end-to-end zmniejsza ryzyko takich awarii.

Ważne jest, aby pisać sensowne testy, a nie tylko te, które zwiększają procent pokrycia kodu. Dobre testy pomagają zapobiec wprowadzeniu uszkodzonego kodu do produkcji, zapewniając, że aplikacja zachowuje się zgodnie z oczekiwaniami nawet po wprowadzeniu zmian. Z mojego doświadczenia wynika, że dobrze napisane testy jednostkowe są zbawienne, pomagając zapobiec katastrofom przy wdrożeniu zmian.

Rozpoczęcie pracy

Zacznijmy od utworzenia katalogu projektu i zainstalowania wymaganych bibliotek. W skład tych bibliotek wchodzą frameworki testowe Mocha, Chai i Sinon. Mocha jest narzędziem do uruchamiania testów, Chai zapewnia bibliotekę do asercji, a Sinon umożliwia tworzenie mocków i stubów zależności.

Inicjalizacja projektu

Utwórz nowy katalog i zainicjuj package.json:

bash

mkdir node-test-tutorial
cd node-test-tutorial
npm init -y

Instalacja zależności
Zainstaluj niezbędne biblioteki do testowania:
bash

npm install mocha chai sinon node-fetch @babel/core @babel/cli @babel/preset-env --save-dev

Ta komenda instaluje Mocha, Chai, Sinon, Babel dla ES6+

Konfiguracja package.json
Zmień sekcję scripts w package.json, aby dodać polecenia do uruchamiania aplikacji i testów:
json

"scripts": {
  "start": "babel-node app/app.js --presets @babel/preset-env",
  "test": "mocha --require @babel/register tests/**/*.spec.js"
}

Teraz możesz uruchomić aplikację za pomocą npm start i testy za pomocą npm test.

Pisanie kodu aplikacji

Stwórzmy małą aplikację, która pobiera dane pogodowe z zewnętrznego API (OpenWeatherMap) i zwraca odpowiednie komunikaty w zależności od temperatury.

api.js (odpowiada za pobieranie danych pogodowych)

javascript

import fetch from 'node-fetch';

/**
 * Fetches weather data for a given city.
 * @param {string} city The city name
 * @returns {Promise<object>} The weather data
 */
export async function getWeatherData(city) {
    const url = `http://api.openweathermap.org/data/2.5/weather?appid=YOUR_API_KEY&units=metric&q=${encodeURIComponent(city)}`;
    try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('API Error');
        return await response.json();
    } catch (error) {
        throw new Error('Network or API Error');
    }
}

app.js (logika głównej aplikacji wykorzystująca dane pogodowe)

javascript

import { getWeatherData } from './api';
export const TEMPERATURE_FREEZING = 'Temperature is below 0, remember to take a coat and scarf!';
export const TEMPERATURE_COLD = 'Temperature is below 10, remember to take a coat!';
export const TEMPERATURE_OK = 'Temperature is okay today.';
export const TEMPERATURE_HOT = 'Warning! Temperature is above 30 degrees, watch for sunburn!';
export const TEMPERATURE_ERROR = 'API Error. Unable to retrieve temperature.';
export const NO_CITY = 'No city specified.';

/**
 * Returns a weather-related message based on the temperature.
 * @param {number} temperature The current temperature in Celsius
 * @returns {string} The appropriate message
 */
export const getTemperatureMessage = (temperature) => {
    if (temperature === undefined) return TEMPERATURE_ERROR;
    if (temperature < 0) return TEMPERATURE_FREEZING;
    if (temperature < 10) return TEMPERATURE_COLD;
    if (temperature < 30) return TEMPERATURE_OK;
    return TEMPERATURE_HOT;
};

/**
 * Fetches weather data for a specified city and returns a temperature message.
 * @param {string} city The city name
 * @returns {Promise<string>} The temperature-related message
 */
export const getWeatherInfo = async (city) => {
    if (!city) return NO_CITY;
    try {
        const weather = await getWeatherData(city);
        return getTemperatureMessage(weather.main.temp);
    } catch (error) {
        return TEMPERATURE_ERROR;
    }
};

Pisanie testów

Teraz napiszemy testy dla metod w app.js. Użyjemy Mocha jako narzędzia do uruchamiania testów, Chai do asercji, a Sinon do tworzenia mocków dla zewnętrznych wywołań API.

Test dla getTemperatureMessage (sprawdzanie różnych zakresów temperatury)

javascript

import * as app from '../app/app';
const { expect } = require('chai');

describe('App.js Tests', () => {
    describe('Check Temperature Messages', () => {
        it('Temperature not specified', () => {
            const message = app.getTemperatureMessage();
            expect(message).to.equal(app.TEMPERATURE_ERROR);
        });

        it('Temperature below 0°C', () => {
            const message = app.getTemperatureMessage(-5);
            expect(message).to.equal(app.TEMPERATURE_FREEZING);
        });

        it('Temperature between 0°C and 10°C', () => {
            const message = app.getTemperatureMessage(5);
            expect(message).to.equal(app.TEMPERATURE_COLD);
        });

        it('Temperature between 10°C and 30°C', () => {
            const message = app.getTemperatureMessage(20);
            expect(message).to.equal(app.TEMPERATURE_OK);
        });

        it('Temperature above 30°C', () => {
            const message = app.getTemperatureMessage(35);
            expect(message).to.equal(app.TEMPERATURE_HOT);
        });
    });
});

Test dla getWeatherInfo z użyciem stubowania Sinon (mockowanie zewnętrznego API)

javascript

import * as app from '../app/app';
import * as api from '../app/api';
const { expect } = require('chai');
const sinon = require('sinon');

describe('getWeatherInfo', () => {
    let weatherApiCallStub;

    beforeEach(() => {
        const fakeResponse = { main: { temp: 15 } };
        weatherApiCallStub = sinon.stub(api, 'getWeatherData').callsFake(() => Promise.resolve(fakeResponse));
    });

    afterEach(() => {
        weatherApiCallStub.restore();
    });

    it('should return the correct temperature message', async () => {
        const message = await app.getWeatherInfo('London');
        expect(message).to.equal(app.TEMPERATURE_OK);
        sinon.assert.calledOnce(weatherApiCallStub);
    });

    it('should handle API errors gracefully', async () => {
        weatherApiCallStub.callsFake(() => Promise.reject(new Error('API Error')));
        const message = await app.getWeatherInfo('InvalidCity');
        expect(message).to.equal(app.TEMPERATURE_ERROR);
    });

    it('should handle no city specified', async () => {
        const message = await app.getWeatherInfo();
        expect(message).to.equal(app.NO_CITY);
        sinon.assert.notCalled(weatherApiCallStub);
    });
});

Podsumowanie

Dzięki połączeniu Mocha, Chai i Sinon udało nam się napisać testy jednostkowe dla naszej aplikacji Node.js. Przetestowaliśmy różne scenariusze, w tym obsługę odpowiedzi z zewnętrznego API za pomocą stubowania Sinon, upewniając się, że aplikacja działa poprawnie w różnych warunkach.

Dobre testy jednostkowe mogą zapobiec katastrofalnym błędom podczas rozwoju i wdrażania aplikacji. Ułatwiają zarządzanie zmianami w kodzie i zapewniają, że aplikacja nadal spełnia wymagania biznesowe. Pamiętaj: nieidealne testy, które uruchamiane są regularnie, są lepsze niż perfekcyjne testy, które nigdy nie zostaną napisane.

Szczęśliwego kodowania i testowania!

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami