{"id":239,"date":"2020-11-02T22:04:50","date_gmt":"2020-11-02T21:04:50","guid":{"rendered":"http:\/\/gieorgijewski.pl\/blog\/?p=239"},"modified":"2020-11-02T22:31:41","modified_gmt":"2020-11-02T21:31:41","slug":"konfiguracja-testow-tdd-w-projekcie-js-z-pakietami-mocha-i-babel","status":"publish","type":"post","link":"https:\/\/gieorgijewski.pl\/blog\/konfiguracja-testow-tdd-w-projekcie-js-z-pakietami-mocha-i-babel\/","title":{"rendered":"Konfiguracja test\u00f3w TDD w projekcie JS z pakietami Mocha i Babel."},"content":{"rendered":"\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"750\" src=\"https:\/\/gieorgijewski.pl\/blog\/wp-content\/uploads\/2020\/10\/Pieter_Bruegel_the_Elder_-_The_Tower_of_Babel_Vienna_-_Google_Art_Project_-_edited-1024x750.jpg\" alt=\"\" class=\"wp-image-242\" srcset=\"https:\/\/gieorgijewski.pl\/blog\/wp-content\/uploads\/2020\/10\/Pieter_Bruegel_the_Elder_-_The_Tower_of_Babel_Vienna_-_Google_Art_Project_-_edited-1024x750.jpg 1024w, https:\/\/gieorgijewski.pl\/blog\/wp-content\/uploads\/2020\/10\/Pieter_Bruegel_the_Elder_-_The_Tower_of_Babel_Vienna_-_Google_Art_Project_-_edited-300x220.jpg 300w, https:\/\/gieorgijewski.pl\/blog\/wp-content\/uploads\/2020\/10\/Pieter_Bruegel_the_Elder_-_The_Tower_of_Babel_Vienna_-_Google_Art_Project_-_edited-768x562.jpg 768w, https:\/\/gieorgijewski.pl\/blog\/wp-content\/uploads\/2020\/10\/Pieter_Bruegel_the_Elder_-_The_Tower_of_Babel_Vienna_-_Google_Art_Project_-_edited.jpg 1280w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n\n\n\n<p>Konfiguracja narz\u0119dzi do budowania projekt\u00f3w w JavaScript bywa czasem czarn\u0105 magi\u0105. Zmuszenie do wsp\u00f3\u0142pracy r\u00f3\u017cnych framework\u00f3w zajmuje du\u017co czasu, ale efekt konfiguracji s\u0142u\u017cy nam potem d\u0142ugo. Dziel\u0105c si\u0119 poni\u017cej swoim wysi\u0142kiem mo\u017ce zaoszcz\u0119dz\u0119 go komu\u015b innemu.<\/p>\n\n\n\n<p>W tym po\u015bcie opisz\u0119 konfiguracj\u0119 narz\u0119dzi <strong>Babel<\/strong> i <strong>Mocha\/Chai<\/strong> do zbudowania i wykonania test\u00f3w typu <strong>BDD\/TDD<\/strong> w przegl\u0105darkowym projekcie JavaScript. Moje testy jednak dotycz\u0105 konkretnych klas i funkcji JS, a nie zachowania si\u0119 UI, poniewa\u017c projekt, w kt\u00f3rym je stosowa\u0142em by\u0142 gr\u0105 rysowan\u0105 na canvasie.<br>Opisz\u0119 r\u00f3wnie\u017c m\u00f3j konkretny use case, bo zdaj\u0119 sobie spraw\u0119, \u017ce konfiguracja nie musi by\u0107 uniwersalna. B\u0119d\u0119 r\u00f3wnie\u017c wtr\u0105ca\u0142 uwagi dla kompletnie pocz\u0105tkuj\u0105cych w \u015bwiecie narz\u0119dzi JS, w kt\u00f3rym sam na pocz\u0105tku nie\u017ale si\u0119 gubi\u0142em.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">M\u00f3j use case: <\/h2>\n\n\n\n<p>Chcia\u0142em utworzy\u0107 testy jednostkowe przy pomocy <strong>Mocha\/Chai<\/strong>. <br>M\u00f3j kod JS to klasy umieszczone oddzielnie ka\u017cda w swoim pliku z <code data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">export default NazwaKlasy;<\/code> na ko\u0144cu. Pliki s\u0105 zebrane i wyeksportowane osobno w jednym pliku <code><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">index.js<\/code><\/code>, tak aby mo\u017cna by\u0142o importowa\u0107 potrzebne klasy w innych plikach z jednego pliku.<br>To tworzy pewne drzewo zale\u017cno\u015bci, kt\u00f3re mo\u017ce by\u0107 do\u015b\u0107 poka\u017ane. Jednak\u017ce w danym te\u015bcie interesuje nas testowanie konkretnej metody z konkretnej klasy &#8211; jej zale\u017cno\u015bci powinny by\u0107 dla tw\u00f3rcy testu przezroczyste. Niestety wtedy (luty 2020), o ile importy by\u0142y zgodnie z moimi oczekiwaniami interpretowane przez przegl\u0105darki, o tyle modu\u0142y Node, kt\u00f3rymi s\u0105 Mocha i Chai, rz\u0105dzi\u0142y si\u0119 swoimi prawami. Z kolei ja, wracaj\u0105c do JS po 15 latach przerwy, nie orientowa\u0142em si\u0119 w zawi\u0142o\u015bciach standard\u00f3w ES, ich historii czy pokryciu przez przegl\u0105darki i Node.js. Pr\u00f3buj\u0105c <strong>wykona\u0107 testy<\/strong> bezpo\u015brednio z Mocha\/Chai otrzymywa\u0142em komunikat:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import { math, AnotherClass, MyOtherClass } from '..\/index.js'\n^^^^^^\nSyntaxError: Cannot use import statement outside a module<\/pre>\n\n\n\n<p>Przyk\u0142adowy plik <code data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">Market.js<\/code> z definicj\u0105 testowanej klasy <em><code data-enlighter-language=\"raw\" class=\"EnlighterJSRAW\">Market<\/code><\/em> wygl\u0105da tak:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import { math, AnotherClass, MyOtherClass } from '..\/index.js'\nclass Market {\n  constructor () {\n    \/\/ code here\n  }\n  methodToTest(input) {\n    let output = 0\n    if (input &lt;= 10) \noutput = -2\n    else if (input &lt;= 40) output = -1\n    return output\n  }\n}\nexport default Market<\/pre>\n\n\n\n<p>I chcia\u0142em \u017ceby plik <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">market-test.js<\/code> z testami wygl\u0105da\u0142 tak:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">var assert = require('assert')\nimport { Market } from '..\/index.js'\n\ndescribe('Market', function() {\n    var market = new Market()\n    describe('#methodToTest()', function() {\n        it('should return 0 if input is greater than 40', function() {\n           assert.equal(market.methodToTest(45), 0)\n        })\n    })\n})<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Moje rozwi\u0105zanie: <\/h2>\n\n\n\n<p>Pierwsze co przysz\u0142o mi do g\u0142owy to u\u017cycie <strong><s>webpacka<\/s><\/strong> (narz\u0119dzia do budowania aplikacji JS-owych), kt\u00f3rego ju\u017c u\u017cywa\u0142em do budowania aplikacji na produkcj\u0119. Okaza\u0142o si\u0119, \u017ce mo\u017cna to zrobi\u0107 pro\u015bciej korzystaj\u0105c tylko z <strong>npm<\/strong>, kt\u00f3rego i tak si\u0119 wykorzystuje do konfiguracji i instalowania zale\u017cno\u015bci w projekcie, <strong>Babela<\/strong>, kt\u00f3ry za\u0142atwia nam kompilowanie JavaScriptu na odpowiedni\u0105 wersj\u0119 oraz samego tandemu <strong>Mocha &#8211; Chai <\/strong>do testowania. <strong>Zalet\u0105<\/strong> tego rozwi\u0105zania by\u0142o te\u017c zmniejszenie liczby plik\u00f3w konfiguracyjnych do dw\u00f3ch i brak tworzenia plik\u00f3w tymczasowych.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Co zatem nale\u017cy po kolei zrobi\u0107:<\/h2>\n\n\n\n<ol class=\"wp-block-list\"><li>Instalacja <strong>Mocha<\/strong> i <strong>Chai<\/strong> za pomoc\u0105 npm:<\/li><\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">npm install --save-dev mocha chai<\/pre>\n\n\n\n<p>Zak\u0142adam, \u017ce projekt zosta\u0142 zainicjowany w npm i plik <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">package.json<\/code> istnieje w katalogu g\u0142\u00f3wnym projektu.<\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"2\"><li>Instalacja niezb\u0119dnych pakiet\u00f3w <strong>Babel<\/strong>:<\/li><\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">npm install --save-dev @babel\/core @babel\/plugin-proposal-class-properties @babel\/preset-env @babel\/register babel-plugin-transform-remove-console<\/pre>\n\n\n\n<ol class=\"wp-block-list\" start=\"3\"><li>Stworzenie w pliku <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">package.json<\/code> wpisu ze zmiennymi \u015brodowiskowymi <strong>Babel<\/strong>:<\/li><\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\"babel\": { \n    \"env\": { \n        \"test-console\": {\n            \"presets\": [ \"@babel\/preset-env\" ],\n            \"plugins\": [ \"@babel\/plugin-proposal-class-properties\" ] \n        }, \n        \"test\": { \n            \"presets\": [ \"@babel\/preset-env\" ], \n            \"plugins\": [ \"@babel\/plugin-proposal-class-properties\", \n                         \"transform-remove-console\" \n                       ] \n        } \n    } \n},<\/pre>\n\n\n\n<p>Wpisu dokonuje si\u0119 na pierwszym poziomie drzewa JSON. <br>Te dwa ustawienia \u015brodowisk r\u00f3\u017cni\u0105 si\u0119 tym, \u017ce w drugim przypadku usuwane s\u0105 wszelkie zapisy <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">console.log<\/code> czyni\u0105c wynik test\u00f3w czytelniejszym. Jest to pewnie dla wielu domy\u015blny spos\u00f3b ich uruchomienia. Drugie ustawienie (<code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">test-console<\/code>) b\u0119dzie pozwala\u0142o na zobaczenie wszystkich zrzut\u00f3w, kt\u00f3rych dokonuj\u0105 testowane metody.<\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"4\"><li>Stworzenie w pliku <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">package.json<\/code> wpis\u00f3w pozwalaj\u0105cych <strong>uruchomi\u0107 za pomoc\u0105 npm<\/strong> testy z r\u00f3\u017cnymi ustawieniami:<\/li><\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\"scripts\": { \n    \"test\": \"BABEL_ENV=test mocha || TRUE\", \n    \"test-watch\": \"BABEL_ENV=test mocha --watch || TRUE\", \n    \"test-console\": \"BABEL_ENV=test-console mocha || TRUE\"\n}<\/pre>\n\n\n\n<p><code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">|| TRUE<\/code> pozwala omin\u0105\u0107 nic niem\u00f3wi\u0105ce b\u0142\u0119dy npm. Je\u015bli jakie\u015b b\u0142\u0119dy pakiet\u00f3w si\u0119 pojawi\u0105, to i tak b\u0119d\u0105 najcz\u0119\u015bciej widoczne od razu w terminalu.<br><strong><code>test-watch<\/code><\/strong> pozwala na \u015bledzenie zmian w plikach i automatyczne odpalanie testu po zapisaniu pliku \u017ar\u00f3d\u0142owego.<\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"5\"><li>I na koniec skonfigurowanie frameworku <strong>mocha<\/strong> z bibliotek\u0105 <strong>chai<\/strong> w pliku <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">.mocharc.js<\/code> (koniecznie uwaga na kropk\u0119 na pocz\u0105tku nazwy pliku) w katalogu g\u0142\u00f3wnym projektu:<\/li><\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">module.exports = { \n    require: ['chai', '@babel\/register'],\n    ui: 'bdd', \n    reporter: 'spec',\n    growl: false,\n};<\/pre>\n\n\n\n<p>Domy\u015blnie mocha zak\u0142ada, \u017ce pliki z testami s\u0105 w katalogu <code data-enlighter-language=\"generic\" class=\"EnlighterJSRAW\">test<\/code><\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"6\"><li>Na koniec nie pozostaje nic innego jak uruchomi\u0107 <strong><code>npm run test<\/code><\/strong> i cieszy\u0107 si\u0119 takim wynikiem:<\/li><\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">> BABEL_ENV=test mocha || TRUE\n\n  Market\n    #methodToTest()\n      \u2713 should return 0 if input is greater than 40 \n\n  1 passing (4ms)<\/pre>\n\n\n\n<p>W razie potrzeby dodatkowych konfiguracji zach\u0119cam do odwiedzenia stron:<br><strong>npm<\/strong> &#8211; <a href=\"https:\/\/docs.npmjs.com\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/docs.npmjs.com<\/a><br><strong>babel<\/strong> &#8211; <a href=\"https:\/\/babeljs.io\/setup\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/babeljs.io\/setup<\/a><br><strong>mocha<\/strong> &#8211; <a href=\"https:\/\/mochajs.org\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/mochajs.org<\/a><br><strong>chai<\/strong> &#8211; <a href=\"https:\/\/www.chaijs.com\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.chaijs.com<\/a><\/p>\n\n\n\n<p>Je\u015bli b\u0119d\u0105 pytania, z ch\u0119ci\u0105 odpowiem na nie w komentarzach.<\/p>\n\n\n\n<p class=\"has-text-align-right\">Mi\u0142ych test\u00f3w!<br>Tomek.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Konfiguracja narz\u0119dzi do budowania projekt\u00f3w w JavaScript bywa czasem czarn\u0105 magi\u0105. Zmuszenie do wsp\u00f3\u0142pracy r\u00f3\u017cnych framework\u00f3w zajmuje du\u017co czasu, ale efekt konfiguracji s\u0142u\u017cy nam potem d\u0142ugo. Dziel\u0105c si\u0119 poni\u017cej swoim wysi\u0142kiem mo\u017ce zaoszcz\u0119dz\u0119 go komu\u015b innemu. W tym po\u015bcie opisz\u0119 konfiguracj\u0119 narz\u0119dzi Babel i Mocha\/Chai do zbudowania i wykonania test\u00f3w typu BDD\/TDD w przegl\u0105darkowym projekcie &hellip; <a href=\"https:\/\/gieorgijewski.pl\/blog\/konfiguracja-testow-tdd-w-projekcie-js-z-pakietami-mocha-i-babel\/\" class=\"more-link\">Czytaj dalej<span class=\"screen-reader-text\"> \u201eKonfiguracja test\u00f3w TDD w projekcie JS z pakietami Mocha i Babel.\u201d<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[35,37,33,34,36],"class_list":["post-239","post","type-post","status-publish","format-standard","hentry","category-javascript","tag-babel","tag-bdd","tag-javascript","tag-mocha","tag-tdd"],"_links":{"self":[{"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/posts\/239","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/comments?post=239"}],"version-history":[{"count":58,"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/posts\/239\/revisions"}],"predecessor-version":[{"id":302,"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/posts\/239\/revisions\/302"}],"wp:attachment":[{"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/media?parent=239"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/categories?post=239"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gieorgijewski.pl\/blog\/wp-json\/wp\/v2\/tags?post=239"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}