Konfiguracja testów TDD w projekcie JS z pakietami Mocha i Babel.

Konfiguracja narzędzi do budowania projektów w JavaScript bywa czasem czarną magią. Zmuszenie do współpracy różnych frameworków zajmuje dużo czasu, ale efekt konfiguracji służy nam potem długo. Dzieląc się poniżej swoim wysiłkiem może zaoszczędzę go komuś innemu.

W tym poście opiszę konfigurację narzędzi BabelMocha/Chai do zbudowania i wykonania testów typu BDD/TDD w przeglądarkowym projekcie JavaScript. Moje testy jednak dotyczą konkretnych klas i funkcji JS, a nie zachowania się UI, ponieważ projekt, w którym je stosowałem był grą rysowaną na canvasie.
Opiszę również mój konkretny use case, bo zdaję sobie sprawę, że konfiguracja nie musi być uniwersalna. Będę również wtrącał uwagi dla kompletnie początkujących w świecie narzędzi JS, w którym sam na początku nieźle się gubiłem.

Mój use case:

Chciałem utworzyć testy jednostkowe przy pomocy Mocha/Chai.
Mój kod JS to klasy umieszczone oddzielnie każda w swoim pliku z export default NazwaKlasy; na końcu. Pliki są zebrane i wyeksportowane osobno w jednym pliku index.js, tak aby można było importować potrzebne klasy w innych plikach z jednego pliku.
To tworzy pewne drzewo zależności, które może być dość pokaźne. Jednakże w danym teście interesuje nas testowanie konkretnej metody z konkretnej klasy – jej zależności powinny być dla twórcy testu przezroczyste. Niestety wtedy (luty 2020), o ile importy były zgodnie z moimi oczekiwaniami interpretowane przez przeglądarki, o tyle moduły Node, którymi są Mocha i Chai, rządziły się swoimi prawami. Z kolei ja, wracając do JS po 15 latach przerwy, nie orientowałem się w zawiłościach standardów ES, ich historii czy pokryciu przez przeglądarki i Node.js. Próbując wykonać testy bezpośrednio z Mocha/Chai otrzymywałem komunikat:

import { math, AnotherClass, MyOtherClass } from '../index.js'
^^^^^^
SyntaxError: Cannot use import statement outside a module

Przykładowy plik Market.js z definicją testowanej klasy Market wygląda tak:

import { math, AnotherClass, MyOtherClass } from '../index.js'
class Market {
  constructor () {
    // code here
  }
  methodToTest(input) {
    let output = 0
    if (input <= 10) 
output = -2
    else if (input <= 40) output = -1
    return output
  }
}
export default Market

I chciałem żeby plik market-test.js z testami wyglądał tak:

var assert = require('assert')
import { Market } from '../index.js'

describe('Market', function() {
    var market = new Market()
    describe('#methodToTest()', function() {
        it('should return 0 if input is greater than 40', function() {
           assert.equal(market.methodToTest(45), 0)
        })
    })
})

Moje rozwiązanie:

Pierwsze co przyszło mi do głowy to użycie webpacka (narzędzia do budowania aplikacji JS-owych), którego już używałem do budowania aplikacji na produkcję. Okazało się, że można to zrobić prościej korzystając tylko z npm, którego i tak się wykorzystuje do konfiguracji i instalowania zależności w projekcie, Babela, który załatwia nam kompilowanie JavaScriptu na odpowiednią wersję oraz samego tandemu Mocha – Chai do testowania. Zaletą tego rozwiązania było też zmniejszenie liczby plików konfiguracyjnych do dwóch i brak tworzenia plików tymczasowych.

Co zatem należy po kolei zrobić:

  1. Instalacja MochaChai za pomocą npm:
npm install --save-dev mocha chai

Zakładam, że projekt został zainicjowany w npm i plik package.json istnieje w katalogu głównym projektu.

  1. Instalacja niezbędnych pakietów Babel:
npm install --save-dev @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/register babel-plugin-transform-remove-console
  1. Stworzenie w pliku package.json wpisu ze zmiennymi środowiskowymi Babel:
"babel": { 
    "env": { 
        "test-console": {
            "presets": [ "@babel/preset-env" ],
            "plugins": [ "@babel/plugin-proposal-class-properties" ] 
        }, 
        "test": { 
            "presets": [ "@babel/preset-env" ], 
            "plugins": [ "@babel/plugin-proposal-class-properties", 
                         "transform-remove-console" 
                       ] 
        } 
    } 
},

Wpisu dokonuje się na pierwszym poziomie drzewa JSON.
Te dwa ustawienia środowisk różnią się tym, że w drugim przypadku usuwane są wszelkie zapisy console.log czyniąc wynik testów czytelniejszym. Jest to pewnie dla wielu domyślny sposób ich uruchomienia. Drugie ustawienie (test-console) będzie pozwalało na zobaczenie wszystkich zrzutów, których dokonują testowane metody.

  1. Stworzenie w pliku package.json wpisów pozwalających uruchomić za pomocą npm testy z różnymi ustawieniami:
"scripts": { 
    "test": "BABEL_ENV=test mocha || TRUE", 
    "test-watch": "BABEL_ENV=test mocha --watch || TRUE", 
    "test-console": "BABEL_ENV=test-console mocha || TRUE"
}

|| TRUE pozwala ominąć nic niemówiące błędy npm. Jeśli jakieś błędy pakietów się pojawią, to i tak będą najczęściej widoczne od razu w terminalu.
test-watch pozwala na śledzenie zmian w plikach i automatyczne odpalanie testu po zapisaniu pliku źródłowego.

  1. I na koniec skonfigurowanie frameworku mocha z biblioteką chai w pliku .mocharc.js (koniecznie uwaga na kropkę na początku nazwy pliku) w katalogu głównym projektu:
module.exports = { 
    require: ['chai', '@babel/register'],
    ui: 'bdd', 
    reporter: 'spec',
    growl: false,
};

Domyślnie mocha zakłada, że pliki z testami są w katalogu test

  1. Na koniec nie pozostaje nic innego jak uruchomić npm run test i cieszyć się takim wynikiem:
> BABEL_ENV=test mocha || TRUE

  Market
    #methodToTest()
      ✓ should return 0 if input is greater than 40 

  1 passing (4ms)

W razie potrzeby dodatkowych konfiguracji zachęcam do odwiedzenia stron:
npmhttps://docs.npmjs.com
babelhttps://babeljs.io/setup
mochahttps://mochajs.org
chaihttps://www.chaijs.com

Jeśli będą pytania, z chęcią odpowiem na nie w komentarzach.

Miłych testów!
Tomek.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *