data.mdx.frontmatter.hero_image

Node – Mockowanie request-ów w testach jednostkowych

2017-11-01 | Frontend, Node, Testy | bd90

Przeglądając mojego bloga mogliście zauważyć kilka rzeczy: nie jestem fanem treningu, kocham kakałko i uważam za niezwykle istotne by testować swoje oprogramowanie. Nie ważne, czy chodzi o testy jednostkowe, integracyjne czy end to end zawsze staram się, aby program napisany przeze mnie był pokryty testami w jak największym stopniu. Miałem nie dawno okazję prowadzić wewnętrzne warsztaty w Polskim Radiu o tematyce testów automatycznych w świecie front-endu. Było to spore wyzwanie, ponieważ przez ostatni rok skupiłem się na poznawaniu narzędzi z technologii .NET Core. Czując się trochę zardzewiały w temacie musiałem trochę nadrobić zaległości. W końcu poziom warsztatu musi być jak mój rozmiar - solidny. Oczywiście, nie chodziło tutaj o poznanie nowych test runner-ów, metodyk, czy innych magicznych sposób na poprawienie jakości testów. Skupiłem się na narzędziach, które są wstanie pomóc w codziennej pracy programisty front-end. Zadałem sobie pytanie, co sprawiało mi największe problemy podczas pisania testów aplikacji front-end? Nie musiałem długo zastanawiać się, by w głowie pojawiły mi się obrazy męczarni w związku z izolacją komponentów pobierających dane za pomocą requestów AJAX. Chcąc ułatwić wam życie chciałbym się z podzielić jednym z moich ostatnich znalezisk na Github-ie - biblioteką Nock, która pozwala na mockowanie request-ów w testach jednostkowych.

Po co mockować requesty w testach?

Zacznijmy od wyjaśnienia w jakim celu w ogóle się to robi. Należy sięgnąć do podstaw teorii konstruowania testów:

  • Testy jednostkowe powinny sprawdzać pojedynczy element oprogramowania
  • Testy jednostkowe powinny być izolowane (jeden test nie powinien wpływać na drugi)
  • Uruchomienie testów jednostkowych powinno być szybkie

Jeżeli uzależnimy testy od danych pobranych z serwera przestają być izolowane. Co w przypadku kiedy serwer zwróci błędem? Nasz komponent powinien mieć obsługę takiego zdarzenia, ale czy na pewno to chcemy przetestować w tym teście? Dodatkowo każde zapytanie do serwera trwa, spowalniając wykonywanie testów.

Nock przybywa na pomoc

Instalacja środowiska testowego

Aby za bardzo nie odbiegać od tematu artykułu postanowiłem użyć bardzo popularnego test runner-a, którym jest Mocha. Jako bibliotekę odpowiedzialną za asercję użyje Chai. Jest to czesto wykorzystywany stack, którego używam z powodzeniem od długiego czasu. Od razu powiem, że nie tutaj poruszać tematów TDD, BDD, Karmy i Nightwatch-a. Prawdopodobnie w dalszych artykułach będzie coś więcej o tym, lecz to jeszcze nie miejsce i czas.

Posiadanie środowiska do uruchamiania testów wymaga od nas zainstalowania w projekcie wspomnianych wcześniej bibliotek. Da się to zrobić za pomocą pojedynczego polecenia z konsoli...

$ npm i -D mocha chai nock

... pod warunkiem, że mamy już projekt, w którym jest zainicjalizowany plik package.json.

Komponent wykorzystujący zapytania AJAX

Testowanie czegoś wymaga tego posiadania. Z mądrego zdania prosta sprawa - trzeba nam kodu. Do komunikacji AJAX użyje bardzo popularnej biblioteki axios. Dodatkowo, aby móc pobrać prawdziwe informacje z jakiegoś API użyjemy usługi jsonplaceholder, która wystawia REST API.

Przykładowy komponent może wyglądać tak:

const axios = require('axios');

class UserComponent {
    constructor() {
        this.name = null;
    }

    load() {
        return axios.get('http://jsonplaceholder.typicode.com/users/1')
            .then((response) => {
                this.name = response.data.name;
            });
    }
}

module.exports = UserComponent;

Cała logika komponentu polega na wykonaniu request-u HTTP po dane użytkownika wystawione przez usługę jsonplaceholder. Z pobranych danych wyjmujemy tylko nazwę użytkownika i przypisujemy ją do "this" tworząc publiczny propertis tego komponentu.

Przechodzimy do napisania testu

Mając gotowy komponent możemy przejść do testowania. Dla ludzi trochę bardziej ogarniętych w temacie - nie przejmujmy się chwilowo brakiem zgodności z TDD. Osobiście uważam, że najpierw trzeba się nauczyć pisania i stosowania testów, a dopiero później przejść do TDD, BDD czy innych rodzajów testowania oprogramowania.

Szybkie przypomnienie składni testów

Bardzo duża liczba test runner-ów dostępnych dla aplikacji pisanych w JS-ie posiada składnie opartą na funkcjach describe / it lub test. Opiera się na tym Mocha i Jasmine, przez co wyglądają bardzo podobnie.

const Component = require('../src/component')
const assert = require('chai').assert

describe('Component', () => {
    describe('#method', () => {
        it('should returns true', () => {
            assert.isTrue(new Component().method())
        })
    })
})

W telegraficznym skrócie: describe ma za zadanie grupować testy w logiczne sekcje. It to pojedynczy test. Osobiście lubię sobie to zobrazować jako rozdziały książki i kartki, gdzie describe to rozdział, pewna struktura która zamyka kartki (it) w pewnej logicznej grupie.

Wykorzystanie biblioteki Nock

Teraz możemy przejść do pierwszego wykorzystania biblioteki Nock. Sama jej składnia jest banalna. Do zmockowania requesta przychodzącego z jsonplaceholder wystarczy kilka lini kodu:

const nock = require('nock');        

const userDTO = { "name": "Kamil" };

nock('http://jsonplaceholder.typicode.com')
    .get('/users/1')
    .reply(200, userDTO);

Jak widać nie wygląda to jak program wyliczający prawdopodobną trajektorię lotu koreańskiej rakiety. Zaczynając od samej góry: musimy oczywiście dodać bibliotekę nock do naszego testu. Tworzymy obiekt DTO, który będzie naszą zmockowaną wartością otrzymaną z request-u. Definiujemy samego mock-a, który zapisuje się na domenę "http://jsonplaceholder.com", na endpoint "/users/1", gdzie ma zostać zwrócony wcześniej zdefiniowany obiekt DTO i kodem odpowiedzi HTTP 200.

Osadzenie Nock-a w teście

Skoro już wiadomo, jak wyglądają testy oraz api biblioteki nock do mockowania request-ów czas na połączenie tej wiedzy. Obecnie staram się mockować odpowiedzi serwera wykorzystując funkcję beforeEach znajdującą się w test runner-ze Mochi, dzięki czemu nie muszę powtarzać kodu, a testy wyglądają dużo czyściej.

Całość wygląda następująco:

const UserComponent = require('../src/UserComponent');
const assert = require('chai').assert;
const nock = require('nock');

describe('User Component', () => {
    beforeEach(() => {
        let user = { "name": "Kamil" };

        nock('http://jsonplaceholder.typicode.com')
            .get('/users/1')
            .reply(200, user);
    });

    it('User Component should fetch data from server', (done) => {
        let userComponent = new UserComponent();
        
        assert.equal(userComponent.name, null);

        userComponent.load().then(() => {
            assert.equal(userComponent.name, "Kamil");
            done();            
        });
    });
});

Sam test wykonuje się bardzo szybko, nie jest zależny od serwera i jest testowany tylko jeden pojedynczy element oprogramowania. Tyle wygrać! :)

Podsumowanie

To by było na tyle jeśli chodzi o ten artykuł. Napisałem dlaczego posiadanie request-ów HTTP w testach jednostkowych uważam za złą praktykę. Przedstawiłem też możliwe rozwiązanie za pomocą biblioteki nock. Oczywiście, same przytoczone powyżej zastosowanie, to tylko początek możliwości tego narzędzia. Poświęcając chwilę na wgłębienie się w dokumentacje dowiecie się o wielu przydatnych funkcjach, jak:

  • Zwracanie odpowiedzi HTTP z kodami błędów
  • Zwracanie nagłówków odpowiedzi HTTP
  • Możliwość ustawienia delay-a aby lepiej symulować zapytanie HTTP

Zachęcam do zapoznania się :)

Mam nadzieje, że artykuł był dla was ciekawy :) Jeżeli chcecie jakiegoś kontentu jak Reksio pragnie szynki to dajcie znać, czy to tutaj w komentarzach czy to przez stronę kontaktową.

Do Następnego!

Cześć.

By Bd90 | 01-11-2017 | Frontend, Node, Testy