Slide 1

Slide 1 text

Javascript Test Dummies image source: http://imgkid.com/crash-test-dummy-symbol.shtml Mauro Verrocchio @maur8ino

Slide 2

Slide 2 text

image source: http://imgkid.com/crash-test-dummies-logo.shtml

Slide 3

Slide 3 text

$ whoami Mauro Verrocchio Senior Frontend Engineer @ Gild twitter: @maur8ino github: @maur8ino likes: #allthingsjavascript #ruby #python #golang #testing #skiing #hiking #playingbasketball ...and a #nerddaddy :)

Slide 4

Slide 4 text

Why talking about frontend testing? ● the so called “Web 2.0” gave a lot of attention to the frontend ● lots of responsive UI and SPA ● tighter release cycles ● tools and frameworks are grown and become mature

Slide 5

Slide 5 text

image source: http://blog.teamtreehouse.com/wp-content/uploads/2014/01/dev-tools-console.png Debugging on frontend image source: http://blogs.sitepointstatic.com/images/tech/520-opera-dragonfly-1.png image source: http://cdn.sixrevisions.com/0228-01_firebug_guide_webdesigners_thumbnail.jpg

Slide 6

Slide 6 text

We will talk about... ● unit testing ● end-to-end testing

Slide 7

Slide 7 text

Unit testing “In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use.” source: https://en.wikipedia.org/wiki/Unit_testing

Slide 8

Slide 8 text

Javascript most notable unit testing framework history ● JSUnit (2001) developed by Pivotal Labs (https://en.wikipedia.org/wiki/JSUnit) ● QUnit (~2006) initially developed by John Resig as a part of jQuery, used to test jQuery, jQuery UI and jQuery Mobile (http://qunitjs.com/) ● Jasmine (2010) developed by Pivotal Labs (http://jasmine.github.io/) ● Mocha (2011) initially developed by TJ Holowaychuk (http://mochajs.org/)

Slide 9

Slide 9 text

Javascript most notable unit testing framework history #2 ● node-tap (2011) developed by TapJS (https://github.com/tapjs/node-tap) ● tape (2012) developed by James Halliday (substack) (https://github.com/substack/tape) ● AVA (2014) developed by Sindre Sorhus (https://github.com/sindresorhus/ava/) More information about Test Anything Protocol (TAP) (https://en.wikipedia.org/wiki/Test_Anything_Protocol)

Slide 10

Slide 10 text

A simple project ● a small library to get users and repositories from Github using the REST api ● a couple of React components https://github.com/maur8ino/fevr-talk-may-2016

Slide 11

Slide 11 text

The Github REST api ● to get user’s repositories list: GET https://api.github.com/users/{username}/repos ● to get user’s specific repository info: GET https://api.github.com/repos/{username}/{reponame} ● more info at https://developer.github.com/v3/

Slide 12

Slide 12 text

The Github REST api #2 ● for unauthenticated requests, the rate limit allows you to make up to 60 requests per hour (https://developer.github.com/v3/#rate-limiting) ● you can cache the response and use ETag or Last-Modified returned values in a subsequent request passing If-None-Match or If-Modified-Since in the header, getting a 304 if the response is not modified; a 304 response does not count against rate limit (https://developer.github.com/v3/#conditional-requests)

Slide 13

Slide 13 text

current window.fetch support source: http://caniuse.com/#feat=fetch window.fetch polyfill https://github.com/github/fetch

Slide 14

Slide 14 text

current Promise(s) support source: http://caniuse.com/#feat=promises Promise polyfill https://github.com/jakearchibald/es6-promise

Slide 15

Slide 15 text

Application structure props: disabled value handleSubmit props: disabled values handleSubmit props: value state: loading repoList selectedUser selectedRepo methods: getUserReposList getUserRepo methods: handleSubmit methods: handleSubmit

Slide 16

Slide 16 text

July 2015 ● XMLHttpRequest ● React v0.13.x ● Babel v5.x ● Karma + Browserify + Babelify + Rewireify + Mocha + Chai + Sinon Then and now... May 2016 ➔ fetch() ➔ React v15.x ➔ Babel v6.x + presets ➔ AVA (jsdom) + fetchMock + babel- plugin-rewire + Sinon

Slide 17

Slide 17 text

React library ● it needs no presentation :) ● has a TestUtils class that makes easy to test components https://facebook.github.io/react/

Slide 18

Slide 18 text

AVA “Futuristic test runner” ● minimal and fast ● simple test syntax ● enforces writing atomic tests ● runs tests concurrently ● no implicit globals https://github.com/sindresorhus/ava

Slide 19

Slide 19 text

Sinon.JS ● standalone test spies, stubs and mocks for JavaScript ● no dependencies, works with any unit testing framework http://sinonjs.org/ http://ricostacruz.com/cheatsheets/sinon.html

Slide 20

Slide 20 text

image source: http://memegenerator.net/instance2/604560

Slide 21

Slide 21 text

Challenges ● running in a concurrent environment may have problems with globals ● sharing the same variable between tests must be passed in a test context ● babel-plugin-rewire must be applied to source files ● shallow rendering does not yet support refs

Slide 22

Slide 22 text

{ [...] "ava": { "require": [ "babel-register", "./test/helpers/setup-browser-env.js" ], "babel": "inherit" }, "babel": { "presets": [ "es2015", "react" ], "env": { "testing": { "plugins": [ "rewire" ] } } }, [...] } package.json

Slide 23

Slide 23 text

{ [...] "scripts": { "test": "NODE_ENV=testing ava --verbose", "test-watch": "NODE_ENV=testing ava --watch", [...] }, [...] } package.json

Slide 24

Slide 24 text

Github library

Slide 25

Slide 25 text

import 'whatwg-fetch'; import {Promise} from 'es6-promise'; const githubBaseURL = 'https://api.github.com'; export function getUserReposListURL(username) { if (!username) { throw new Error('Username is undefined, null or an empty string'); } return `${githubBaseURL}/users/${encodeURIComponent(username)}/repos`; }; src/github.js

Slide 26

Slide 26 text

import test from 'ava'; import * as github from '../src/github'; test('should generate the list of repositories url', t => { t.is(github.getUserReposListURL('maur8ino'), 'https://api.github.com/users/maur8ino/repos'); t.is(github.getUserReposListURL('maur/8i&no'), 'https://api.github.com/users/maur%2F8i%26no/repos'); }); test/github-test.js

Slide 27

Slide 27 text

[...] test('should throw an error', t => { let fn = github.getUserReposListURL.bind( github.getUserReposListURL, undefined ); t.throws(fn, /empty string/); fn = github.getUserReposListURL.bind( github.getUserReposListURL, null ); t.throws(fn, /empty string/); fn = github.getUserReposListURL.bind( github.getUserReposListURL, '' ); t.throws(fn, /empty string/); }); test/github-test.js

Slide 28

Slide 28 text

const cache = {}; const get = (url) => { let options = {}; if (cache[url] && cache[url].ETag) { options.headers = { 'If-None-Match': cache[url].ETag }; } return fetch(url, options).then((response) => { if (response.status === 200) { if (response.headers.get('ETag')) { cache[url] = { ETag: response.headers.get('ETag') }; } return response.json(); } else if (response.status === 304) { if (cache[url].json) { return Promise.resolve(cache[url].json); } } else { return Promise.reject(Error('Error', response)); } }).then((json) => { if (cache[url]) { cache[url].json = json; } return Promise.resolve(json); }); }; src/github.js

Slide 29

Slide 29 text

export function getUserReposList(username) { try { return get(getUserReposListURL(username)); } catch(error) { return Promise.reject(error); } }; src/github.js

Slide 30

Slide 30 text

[...] import fetchMock from 'fetch-mock'; [...] test.afterEach(() => { fetchMock.restore(); }); [...] test.serial('should make an ajax request', t => { fetchMock.mock('https://api.github.com/users/maur8ino/repos', 'GET', [ { "id": 35957173, "name": "angular-post-message" }, { "id": 37024234, "name": "react-bem-mixin" } ]); return github.getUserReposList('maur8ino').then(response => { t.deepEqual(response, [ { id: 35957173, name: 'angular-post-message' }, { id: 37024234, name: 'react-bem-mixin' } ]); }); }); test/github-test.js

Slide 31

Slide 31 text

React Components

Slide 32

Slide 32 text

global.document = require('jsdom').jsdom(''); global.window = document.defaultView; global.navigator = window.navigator; test/helpers/setup-browser-env.js

Slide 33

Slide 33 text

import React from 'react'; const SearchForm = React.createClass({ handleSubmit(e) { e.preventDefault(); this.props.handleSubmit(this.refs.input.value); }, render() { let {value, disabled} = this.props; return ( Search ); } }); export default SearchForm; src/SearchForm.jsx

Slide 34

Slide 34 text

import test from 'ava' import sinon from 'sinon'; import React from 'react'; import ReactDOM from 'react-dom'; import {Simulate, renderIntoDocument, findRenderedDOMComponentWithTag} from 'react-addons-test-utils'; import SearchForm from '../src/SearchForm.jsx'; test('should be initialized with a default value', t => { const searchForm = renderIntoDocument(); const input = findRenderedDOMComponentWithTag(searchForm, 'input'); t.is(input.value, 'maur8ino'); }); test/SearchForm-test.jsx

Slide 35

Slide 35 text

[...] test('should disable the form', t => { const searchForm = renderIntoDocument(); const input = findRenderedDOMComponentWithTag(searchForm, 'input'); const button = findRenderedDOMComponentWithTag(searchForm, 'button'); t.true(input.disabled); t.true(button.disabled); }); test('should handle the form submit', t => { const handleSubmit = sinon.spy(); const searchForm = renderIntoDocument( ); Simulate.submit(ReactDOM.findDOMNode(searchForm)); t.true(handleSubmit.calledOnce); t.true(handleSubmit.calledWith('maur8ino')); }); test/SearchForm-test.jsx

Slide 36

Slide 36 text

Unit testing with AngularJS https://github.com/maur8ino/aduno-talk-september-2015

Slide 37

Slide 37 text

Tip of the day: “Proof of concept: make sure your tests work by letting them fail under different circumstances.”

Slide 38

Slide 38 text

End-to-end testing “System testing [End-to-end] of software or hardware is testing conducted on a complete, integrated system to evaluate the system's compliance with its specified requirements. System testing [End-to-end] falls within the scope of black box testing, and as such, should require no knowledge of the inner design of the code or logic.” source: https://en.wikipedia.org/wiki/System_testing

Slide 39

Slide 39 text

Selenium testing suite ● initially developed at ThoughtWorks by Jason Huggins in 2004 ● automate web browsers across many platforms ● de-facto standard for testing end-to-end web applications ● Selenium Webdriver, maybe the main reason why Selenium is popular, accepts command in “Selenese” and communicates with real browser ● Selenium IDE has a Firefox addon to record, edit and playback browser interaction ● Selenium Grid acts as a hub for Webdriver instances http://docs.seleniumhq.org/

Slide 40

Slide 40 text

Cucumber BDD tool ● lets you write feature file in natural language (Gherkin) ● implemented in Ruby, Javascript, Java, Python, .NET, etc.. http://cucumber.io/

Slide 41

Slide 41 text

Feature file ● has a Feature keyword, which explains the feature ● can have a Background list of steps to do for every Scenario ● has one or more Scenarios ● every step of Background and Scenarios starts with Given, When, Then, But or And keywords; there is no actual difference between them, they let you describe even better the feature

Slide 42

Slide 42 text

Step definition file ● one or more step in feature file can be resolved by one step definition (because of regular expression) ● lets you use all the power of the language ● drives real browser via selenium or headless browser like zombiejs or phantomjs

Slide 43

Slide 43 text

Test end-to-end with Cucumber ● npm install --save-dev cucumber selenium- webdriver ● feature files will be placed in features/ ● step definition files will be placed in features/steps ● support files will placed in features/support

Slide 44

Slide 44 text

image source: http://memegenerator.net/instance/62951843

Slide 45

Slide 45 text

Integration with a CI server .travis.yml http://docs.travis-ci.com/user/languages/javascript-with-nodejs/ language: node_js node_js: - “5.1” - “4.2” - “...” env: - MY_ENV_VAR=”something”

Slide 46

Slide 46 text

Final take: let the machine do the machine work

Slide 47

Slide 47 text

What’s next... ● test coverage (Istanbul - https://gotwarlost.github.io/istanbul/) (nyc - https://github.com/bcoe/nyc) ● code quality (bitHound - https://www.bithound.io/) (Code Climate - https://codeclimate.com/)

Slide 48

Slide 48 text

Useful links ● http://stackoverflow.com/a/680713 Javascript unit test frameworks overview ● https://github.com/cucumber/cucumber-js the “official” homepage of cucumberjs with examples ● http://devdocs.io/ awesome documentation ● http://kangax.github.io/compat-table/es6/ super useful es6 table comparison ● http://svgporn.com/ developer tools icon in svg

Slide 49

Slide 49 text

“It is now two decades since it was pointed out that program testing may convincingly demonstrate the presence of bugs, but can never demonstrate their absence. After quoting this well-publicized remark devoutly, the software engineer returns to the order of the day and continues to refine his testing strategies, just like the alchemist of yore, who continued to refine his chrysocosmic purifications.” (E. W. Dijkstra, 1988) source: https://www.cs.utexas.edu/~EWD/transcriptions/EWD10xx/EWD1036.html Quote of the day:

Slide 50

Slide 50 text

source: http://2.bp.blogspot.com/_7OeU3GAGUBI/S9DZdiChuoI/AAAAAAAAACE/-CJ6H_oL2Ck/s1600/thats+all+folks.jpg Thanks!