Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Javascript Test Dummies

May 19, 2016

Javascript Test Dummies

Mauro Verrocchio @ FEVr


May 19, 2016

More Decks by FEVR

Other Decks in Technology


  1. $ whoami Mauro Verrocchio Senior Frontend Engineer @ Gild twitter:

    @maur8ino github: @maur8ino likes: #allthingsjavascript #ruby #python #golang #testing #skiing #hiking #playingbasketball ...and a #nerddaddy :)
  2. 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
  3. 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
  4. 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/)
  5. 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)
  6. 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
  7. 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/
  8. 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)
  9. <GithubSearch/> Application structure <SearchForm/> <SelectForm/> <DisplayInfo/> props: disabled value handleSubmit

    props: disabled values handleSubmit props: value state: loading repoList selectedUser selectedRepo methods: getUserReposList getUserRepo methods: handleSubmit methods: handleSubmit
  10. 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
  11. React library • it needs no presentation :) • has

    a TestUtils class that makes easy to test components https://facebook.github.io/react/
  12. 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
  13. 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
  14. 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
  15. { [...] "ava": { "require": [ "babel-register", "./test/helpers/setup-browser-env.js" ], "babel":

    "inherit" }, "babel": { "presets": [ "es2015", "react" ], "env": { "testing": { "plugins": [ "rewire" ] } } }, [...] } package.json
  16. 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
  17. 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
  18. [...] 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
  19. 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
  20. [...] 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
  21. 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 ( <form className="username" onSubmit={this.handleSubmit}> <input type="text" ref="input" defaultValue={value} disabled={disabled} /> <button type="submit" disabled={disabled}>Search</button> </form> ); } }); export default SearchForm; src/SearchForm.jsx
  22. 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(<SearchForm value="maur8ino" />); const input = findRenderedDOMComponentWithTag(searchForm, 'input'); t.is(input.value, 'maur8ino'); }); test/SearchForm-test.jsx
  23. [...] test('should disable the form', t => { const searchForm

    = renderIntoDocument(<SearchForm disabled={true} />); 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( <SearchForm handleSubmit={handleSubmit} value="maur8ino" /> ); Simulate.submit(ReactDOM.findDOMNode(searchForm)); t.true(handleSubmit.calledOnce); t.true(handleSubmit.calledWith('maur8ino')); }); test/SearchForm-test.jsx
  24. Tip of the day: “Proof of concept: make sure your

    tests work by letting them fail under different circumstances.”
  25. 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
  26. 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/
  27. Cucumber BDD tool • lets you write feature file in

    natural language (Gherkin) • implemented in Ruby, Javascript, Java, Python, .NET, etc.. http://cucumber.io/
  28. 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
  29. 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
  30. 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
  31. 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/)
  32. 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
  33. “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: