Testing Javascript
How to stop worrying and start testing
Nordnet Academy

jQuery('a.submit').on('click', function() {
// do something useful
});
- Coupled with DOM and jQuery
- Runs in a browser, hard to automate
- Manual test all the things!
Testing JS

(function() {
'use strict';
angular
.module('mobileapp')
.controller('SettingsController', SettingsController);
/* @ngInject */
function SettingsController(storageservice) {
var vm = this;
vm.country = storageservice.get('country');
}
})();
Testing JS

Testing JS

- More complex frameworks
- More complex applications
- Complicated manual testing
- Harder to make changes
Testing JS

- Improve quality
- Faster feedback
- Keep old code working
- Documentation
Frameworks
Frameworks
-
Nightwatch
- e2e testing in the browser
Test structure
describe('what function/lib/file is being tested', () => {
describe('when - given a certain condition', () => {
// use to set up Given and When
beforeEach(() => {
// set up test conditions
});
// use to assert results - Then
it('should result in something', () => expect(something).to.be.defined);
afterEach(() => {
// clean up
});
});
});
Mocha
describe('tests', function() {
before(function() {
// runs before all tests in this block
});
after(function() {
// runs after all tests in this block
});
beforeEach(function() {
// runs before each test in this block
});
afterEach(function() {
// runs after each test in this block
});
// test cases
});
Mocha
describe('tests', function() {
beforeEach(function() {
// beforeEach hook
});
beforeEach(function namedFunction() {
// beforeEach:namedFunction
});
beforeEach('some description', function() {
// beforeEach:some description
});
});
Mocha
describe('tests', function() {
beforeEach(function(done) {
asyncPrepare().then(done);
});
it('should execute async operation', function(done) {
asyncOperation.then(response => {
expect(response).to.be.ok;
done();
});
});
});
Sinon - spy
- Function that records
- arguments
- return value
- this
- exceptions thrown
- for all calls
// anonymous function spy
const spy = sinon.spy();
// spy for given function
const spySomeFunc = sinon.spy(someFunc);
// spy on specific method
const spyGet = sinon.spy(api, 'get');
Sinon - stub
- Functions with pre-programmed behavior
- Full support for spy API
- Additional methods to alter stub behavior
- Force code flow to a specific path
- Avoid calling the real function
Sinon - stub
// anonymous function stub
const stub = sinon.stub();
// replace function with stub
const stubGet = sinon.stub(api, 'get');
// replace function with new function wrapped in a spy
const stubGetResolved =
sinon.stub(api, 'get', () => Promise.resolve({ answer: 42 }));
// restore original method - important to clean up!
api.get.restore();
Sinon - mocks
- Functions with pre-programmed behavior and expectations
- Fails test is not used as defined by expectations
- Full support for spy and stub APIs
Sinon - sandbox
- Simplifies restoring fakes after tests
- fake XHR, timers, spies, stubs
- sinon.sandbox.create()
- sandbox.restore()
Sinon - more tools
- Fake timers
- Fake XHR
- Fake server
- Assertions
- Matchers
Chai
- Assertion library
- BDD or TDD style
- assert()
- expect()
- should()

Chai
expect(3).to.equal(3);
expect(1).to.not.be.true;
expect(someObj).to.be.defined;
// deep equal
expect({ foo: 'bar' }).to.eql({ foo: 'bar'});
expect([1, 2, 3]).to.eql([1, 2, 3]);
// arrays
expect([1,2,3]).to.include(2);

Testing React
Testing React
- Render into a DOM node
- Stub dependencies
- Verify content
- Verify state and props
- Verify triggers actions/events
Testing React
import React from 'react';
class NewsItem extends React.Component {
render() {
return (
<li>{ this.props.newsItem.headline }</li>
);
}
}
NewsItem.propTypes = {
newsItem: React.PropTypes.object.isRequired,
};
export default NewsItem;
Testing React
import React from 'react/addons';
import NewsItem from './../news-item';
// React 0.14
// import TestUtils from 'react-addons-test-utils';
const TestUtils = React.addons.TestUtils;
describe('NewsItem', () => {
let node;
const headline = 'abc';
beforeEach(() => {
const component =
TestUtils.renderIntoDocument(
<NewsItem newsItem={ {headline} } />);
node = React.findDOMNode(component);
});
it('should display headline',
() => expect(node.textContent).to.equal(headline));
it('should render li node',
() => expect(node.tagName).to.equal('LI'));
});
Testing React
class NewsItem extends React.Component {
componentDidMount() {
this.props.mounted();
}
...
}
import React from 'react/addons';
import NewsItem from './../news-item';
const TestUtils = React.addons.TestUtils;
describe('NewsItem', () => {
let spy;
beforeEach(() => {
spy = sinon.spy();
const component =
TestUtils.renderIntoDocument(
<NewsItem newsItem={} mounted={ spy } />);
node = React.findDOMNode(component);
});
it('should call mounted', () => expect(spy).to.have.been.called);
});
Testing React
class NewsItem extends React.Component {
render() {
return (
<a onClick={ this.props.clicked }>{ this.props.newsItem.headline }</a>
);
}
}
import React from 'react/addons';
import NewsItem from './../news-item';
const TestUtils = React.addons.TestUtils;
describe('NewsItem', () => {
let node;
let spy;
beforeEach(() => {
spy = sinon.spy();
node = renderComponent(
<NewsItem newsItem={} clicked={ spy } />);
TestUtils.Simulate.click(node);
});
it('should call clicked', () => expect(spy).to.have.been.called);
});
Testing React
-
TestUtils docs
- more methods in the API
- Differences between 0.13 and 0.14
- react-addons-test-utils
- react-dom
End-to-End Testing
Nightwatch.js

- Node.js base e2e testing
-
Similar to Fluentlenium, but JS
Nightwatch.js

- nightwatch.json
- basic configuration
-
nightwatch.js
-
starts up tests
-
-
e2e-tests/
-
test files
-
page objects
-
Page objects

// e2e-tests/page-objects/news-page.js
export default {
url: 'http://localhost:9000',
elements: {
getMoreNewsButton: {
selector: 'button'
}
}
}
e2e tests

/* e2e-tests/tests/get-more-news-button.js */
const beforeEach = (client) => {
const newsPage = client.page['news-page']();
newsPage
.navigate()
.waitForElementVisible('body', 1000);
};
const displaysGetMoreNewsButton = (client) => {
client.expect.element('button').to.be.present;
client.expect.element('button').to.contain.text('Get more news');
client.end();
};
export default {
beforeEach,
'Get more news button is displayed': displaysGetMoreNewsButton,
};
Commands

// e2e-tests/page-objects/news-page.js
const commands = {
loadMore: function() {
return this.click('button');
},
};
export default {
url: 'http://localhost:9000',
commands: [commands],
elements: {
getMoreNewsButton: {
selector: 'button'
}
}
}
e2e tests

/* e2e-tests/tests/get-more-news-button.js */
const url = 'http://localhost:9000/';
let newsPage;
const beforeEach = client => {
newsPage = client.page['news-page']();
newsPage
.navigate()
.waitForElementVisible('body', 1000);
};
const loadsMoreNewsOnButtonClick = client => {
newsPage.loadMore();
// TOOD await and assert
client.end();
};
export default {
beforeEach,
'Loads more news on button click': loadsMoreNewsOnButtonClick,
};
Testing JS


Questions?

Testing Javascript: How to stop worrying and start testing
By nordnetacademy
Testing Javascript: How to stop worrying and start testing
Introduction to testing in Javascript
- 1,362