Unit tesing
sebastian.sztyper@kotrak.com
#UX #USABILITY #FRONTEND
Sebastian Sztyper
@ssztyper
ProgramistaMag
Agenda
What is testing
Ways of testing
Patterns
Best practise
Frameworks
Live examples
Tools
No test come out on production
git push 16:00 :)
ISTQB
Testing is information about PRODUCT QUALITY
TESTING
Alpha testing
Beta testing
Human testing
Negative testing
Fuzz testing
Top down testing
Scalability testing
Exploratory testing
Active testing
Endurance testing
Interface testing
Interface testing
Age testing
Loop testing
Recorvery testing
Recorvery testing
Regresion testing
Component testing
Intergation testing
Data flow testing
User story testing
Smoke testing
Big-bang testing
Domain testing
Acceptance testing
Safety testing
Boundary value testing
All pair testing
Conversion testing
Accourancy testing
Top-down testing
Czym jest testowanie dla
Other
End to end
Integration and UI
Regression
Czym jest testowanie dla
Integration testing
developers
What is testing for
What exactly is the test?
A comparison of the assumed value
in relation to the one we receive
A set of sets, values, procedures and classes grouped into
a template and providing us with appropriate functionality
and business value
What exactly is the unit?
test suit
test condition
test case
test comparison
Unit test modules
import nock from 'nock';
import expect from 'expect';
import configureMockStore from 'redux-mock-store';
import { createEpicMiddleware } from 'redux-observable';
import { fetchUserEpic, fetchUser, FETCH_USER } from '../../redux/modules/user';
const epicMiddleware = createEpicMiddleware(fetchUserEpic);
const mockStore = configureMockStore([epicMiddleware]);
describe('fetchUserEpic', () => {
let store;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(fetchUserEpic);
});
it('produces the user model', () => {
const payload = { id: 123 };
nock('http://example.com/')
.get('/api/users/123')
.reply(200, payload);
store.dispatch({ type: FETCH_USER });
expect(store.getActions()).toEqual([
{ type: FETCH_USER },
{ type: FETCH_USER_FULFILLED, payload }
]);
});
});
Example
React/Redux
@Injectable()
export class MasterService {
constructor(private masterService: ValueService) { }
getValue() { return this.masterService.getValue(); }
}
Example
Angular
describe('MasterService without Angular testing support', () => {
let masterService: MasterService;
it('#getValue should return real value from the real service', () => {
masterService = new MasterService(new ValueService());
expect(masterService.getValue()).toBe('real value');
});
it('#getValue should return faked value from a fakeService', () => {
masterService = new MasterService(new FakeValueService());
expect(masterService.getValue()).toBe('faked service value');
});
it('#getValue should return stubbed value from a spy', () => {
// create `getValue` spy on an object representing the ValueService
const valueServiceSpy =
jasmine.createSpyObj('ValueService', ['getValue']);
// set the value to return when the `getValue` spy is called.
const stubValue = 'stub value';
valueServiceSpy.getValue.and.returnValue(stubValue);
masterService = new MasterService(valueServiceSpy);
expect(masterService.getValue())
.toBe(stubValue, 'service returned stub value');
expect(valueServiceSpy.getValue.calls.count())
.toBe(1, 'spy method was called once');
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
.toBe(stubValue);
});
});
always?
new person in the project
great and short code documentation
big picture of the project
the project is easier to develop
protection against regression
Unit tests
make
life
easier
Patterns and antypatterns
As few conditions as possible
As little as possible
Writing a test for testing
Test reviews - tip - is faster than code review
AAA
Arrange Act Assert
function OurTestMethod_WhichSpecificData_SpecificBehaviour()
{
//Arrange
var systemUnderTests = Factory.CreateSystemUnderTest();
var specificData = DataFactory.CreateSpecificData();
//Act
systemUnderTests.OurTestMethod(specificData);
//Assert
assert.ThatSpecificBehaviour;
}
Test after
Production version
Inheritance code
TEST FIRST
TEST AFTER
TEST DURING (TDD)
ETC.. :)
Test before
new code
:)
TDD
Red
Green
Refactor
var calculator = {
sum: NOT_IMPLEMENTED
//sum: function(a, b) { return a + b; }
};
// specs code
describe("calculator", function() {
it("sum method should be implemented", function() {
expect(calculator.sum).toBeDefined();
});
it("sum method should sum values", function() {
expect(calculator.sum(1,2)).toEqual(3);
});
});
var NOT_IMPLEMENTED = undefined;
(function() {
var env = jasmine.getEnv();
env.addReporter(new jasmine.HtmlReporter());
env.execute();
}());
TEST
FIRST
Before we write the code, we design
and test what is to be created.
We think like end user.
Integration-scalability.
New edge cases are particularly critical
thrustworhly consistend
Unit test should be
repetable
seperated
easy to write and read
fast and short
Loop and branches - bugs
var stringHtml = '';
for(var i = 0 ; i < 15000; i++) {
stringHtml += '<li id='+ i +'><a><a></li>'
}
return stringHtml;
EXTERNAL RESOURCES (.json) ! :)
We do not comment on the tests
We remove dead artifacts
Refactoring
Good practise
Test tools
Ide a testing structure
(Mocha, Jasmine, Jest, Cucumber)
Provide assertions functions
(Chai, Jasmine, Jest, Unexpected)
Generate, display, and watch test results
(Mocha, Jasmine, Jest, Karma)
Generate and compare snapshots of component
and data structures to make sure changes from previous runs are intended (Jest, Ava)
Provide mocks, spies, and stubs
(Sinon, Jasmine, enzyme, Jest, testdouble)
Generate code coverage reports
(Istanbul, Jest, Blanket)
Provide a browser or browser-like environment
with a control on their scenarios execution
(Protractor, Nightwatch, Phantom, Casper)
More details
Testing structure refers to the organization of your tests. Nowdays, tests are usually organized
in a BDD structure that supports behavior-driven development (BDD).
describe('calculator', function() {
// describes a module with nested "describe" functions
describe('add', function() {
// specify the expected behavior
it('should add 2 numbers', function() {
//Use assertion functions to test the expected behavior
...
})
})
})
Assertion functions are functions that make sure
that tested variables contain the expected value.
They usually looks like this, where the most popular are the first two:
// Chai expect (popular)
expect(foo).to.be.a('string')
expect(foo).to.equal('bar')
// Jasmine expect (popular)
expect(foo).toBeString()
expect(foo).toEqual('bar')
// Chai assert
assert.typeOf(foo, 'string')
assert.equal(foo, 'bar')
// Unexpected expect
expect(foo, 'to be a', 'string')
expect(foo, 'to be', 'bar')
Spies provide us with information about functions.
How many times were they called, in what cases, and by whom?
They are used in integration tests to make sure that the side effects of a process are as expected.
it('should call method once with the argument 3', () => {
// create a sinon spy to spy on object.method
const spy = sinon.spy(object, 'method')
// call the method with the argument "3"
object.method(3)
// make sure the object.method was called once, with the right arguments
assert(spy.withArgs(3).calledOnce)
})
Stubbing or dubbing (like doubles in movies) replaces selected functionswith selected functions to ensure an expected behavior on selected modules
// Sinon
sinon.stub(user, 'isValid').returns(true)
// Jasmine stubs are actually spies with stubbing functionallity
spyOn(user, 'isValid').andReturns(true)
Promises
it('resolves with the right name', done => {
// make sure User.fetch "responds" with our own value "David"
const stub = sinon
.stub(User.prototype, 'fetch')
.resolves({ name: 'David' })
User.fetch()
.then(user => {
expect(user.name).toBe('David')
done()
})
})
Mocks or Fakes are faking certain modules or behaviors to test different parts of a processes.
Sinon can, for example, fake a server to ensure offline, fast and expected responses when testing a process.
it('returns an object containing all users', done => {
const server = sinon.createFakeServer()
server.respondWith('GET', '/users', [
200,
{ 'Content-Type': 'application/json' },
'[{ "id": 1, "name": "Gwen" }, { "id": 2, "name": "John" }]'
])
Users.all()
.done(collection => {
const expectedCollection = [
{ id: 1, name: 'Gwen' },
{ id: 2, name: 'John' }
]
expect(collection.toJSON()).to.eql(expectedCollection)
done()
})
server.respond()
server.restore()
})
Snapshot Testing is when you compare a data
structure to an expected one.
it('renders correctly', () => {
// create an instance of the Link component with page and child text
const linkInstance = (
<Link page="http://www.facebook.com">Facebook</Link>
)
// create a data snapshot of the component
const tree = renderer.create(linkInstance).toJSON()
// compare the sata to the last snapshot
expect(tree).toMatchSnapshot()
})
Putting
it All Together
unit and integration tests
UI tests
Two task runners
testing tools
General prominent
jsdom
In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"
jsdom
More:
It is worth mentioning that the JS community rapidly
improves it and the current version is very close to a real browser.
You can’t take a screenshot for example (inprogress developed)
Convenience APIs
Canvas support
Running jsdom inside a web browser (browserify)
Istanbul
Coverage code %
More
Istanbul will tell you how much of your code is covered with unit tests. It will report on statement, line, function and branch coverage in percentages so you will understand better what is left to cover.
Support for the most popular JavaScript testing frameworks (see our tutorials).
Support for instrumenting subprocesses, using the nyc command-line-interface.
Karma lets you run tests in browser and browser like environments including jsdom.
Karma hosts a test server with a special web page to run your tests in the page’s environment. This page can be run across many browsers.
This also means tests can be run remotely using services like BrowserStack.
Continuous Integration
Popular assertion library
var chai = require('chai');
var assert = chai.assert; // Using Assert style
var expect = chai.expect; // Using Expect style
var should = chai.should(); // Using Should style
import { assert } from 'chai'; // Using Assert style
import { expect } from 'chai'; // Using Expect style
import { should } from 'chai'; // Using Should style
SINON.JS
Standalone test spies, stubs and mocks for JavaScript.
Works with any unit testing framework.
SINON.JS
Spies
import sinon from 'sinon'
it('calls the original function', function () {
var callback = sinon.spy();
var proxy = once(callback);
proxy();
assert(callback.called);
});
SINON.JS
Fake server
var server;
before(function () { server = sinon.fakeServer.create(); });
after(function () { server.restore(); });
it("calls callback with deserialized data", function () {
var callback = sinon.spy();
getTodos(42, callback);
// This is part of the FakeXMLHttpRequest API
server.requests[0].respond(
200,
{ "Content-Type": "application/json" },
JSON.stringify([{ id: 1, text: "Provide examples", done: true }])
);
assert(callback.calledOnce);
});
wallaby.js
wallaby.js
fast
IDE support
wallaby.js
test runner IDE
Cucumber help with writing tests in BDD by dividing them between the acceptance criteria files using the Gherkin syntax and the tests that are correspondent to them.
Gherkin syntax:
Feature: A reader can share an article to social networks
As a reader
I want to share articles
So that I can notify my friends about an article I liked
Scenario: An article was opened
Given I'm inside an article
When I share the article
Then the article should change to a "shared" state
module.exports = function() {
this.Given(/^I'm inside an article$/, function(callback) {
...
})
this.When(/^I share the article$/, function(callback) {
...
})
this.Then(/^the article should change to a "shared" state$/, function(callback) {
...
})
}
Generate:
Testing frameworks JS
function add(a, b) {
return a + b;
}
module.exports = add;
const add = require('./add');
describe('add', () => {
it('should add two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
Code
Test
Result
Console
simple, flexible, fun
Mocha is currently the most used library. Unlike Jasmine, it is used with third party assertion, mocking, and spying tools (usually Enzyme and Chai).
More flexible and open to extensions
Community Has many plugins and extension to test unique scenarios.
Extensibility Plugins, extensions and libraries such as Sinon includes features Jasmine does not have.
Globals
Jasmine is the testing framework that Jest is based on.
It has been around for a longer time and has a huge amount of articles and tools that were created by the community.
Also, Angular still suggests using it over Jest, although Jest is perfectly suitable to run Angular tests as well, and many people do it.
Ready-To-Go Globals Community Angular
Minimalistic testing library
that runs tests in parallel.
import test from 'ava'
test('arrays are equal', t => {
t.deepEqual([1, 2], [1, 2])
})
Globals
Simplicity
Development
Speed
Snapshot testing
Tape
Tape
Simplicity - Minimalistic structure
and assertions without a complex API.
Even more than Ava.
Globals
No Shared State between tests
No CLI is needed
- Tape is simply run anywhere
JS can be run.
var test = require('tape');
test('timing test', function (t) {
t.plan(2);
t.equal(typeof Date.now, 'function');
var start = Date.now();
setTimeout(function () {
t.equal(Date.now() - start, 100);
}, 100);
});
$ node example/timing.js
TAP version 13
# timing test
ok 1 should be equal
not ok 2 should be equal
---
operator: equal
expected: 100
actual: 107
...
1..2
# tests 2
# pass 1
# fail 1
Choose Your Unit
In short, if you want to “just get started” or looking for a fast framework for large projects, go with Jest.
If you want a very flexible and extendable configuration, go with Mocha.
If you are looking for simplicity go with Ava.
If you want to be really low-level, go with tape.
and Integration Tests Framework
SUMMARY
UI Testing Tools
Selenium
Node.js <=> WebDriver <=> Selenium Server <=> FF/Chrome/IE/Safari
describe('login form', () => {
before(() => {
return driver.navigate().to('http://path.to.test.app/')
})
it('autocompletes the name field', () => {
driver
.findElement(By.css('.autocomplete'))
.sendKeys('John')
driver.wait(until.elementLocated(By.css('.suggestion')))
driver.findElement(By.css('.suggestion')).click()
return driver
.findElement(By.css('.autocomplete'))
.getAttribute('value')
.then(inputValue => {
expect(inputValue).to.equal('John Doe')
})
})
after(() => {
return driver.quit()
})
})
Libraries for
selenium ecosystem
So if you use Selenium or Selenium based tools, you can also use Apium to test
on mobile devices.
APIUM ECOSYSTEM
Has special hooks, although can be successfully used with other JS frameworks too. Angular official documentation suggests using this tool.
Error reporting - Good mechanism.
Support - TypeScript support is available and the library is operated and maintained by the huge Angular team.
All versions Angular
Very easy and readable.
Very simple and agnostic from even being
used for tests, flexible and extensible library.
Very good support and enthusiastic developer community that makes it reach with plugins and extensions.
Syntax
Flexible
Community
module.exports = {
'demo test google' : function (client) {
client
.url('http://google.com')
.waitForElementPresent('body', 1000);
},
'part two' : function(client) {
client
.setValue('input[type=text]', ['nightwatch', client.Keys.ENTER])
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
module.exports = {
tags: ['git'],
'Demo test GitHub' : function (client) {
client
.url('https://github.com/nightwatchjs/nightwatch')
.waitForElementVisible('body', 1000)
.assert.visible('.container h1 strong a')
.assert.containsText(
'.container h1 strong a',
'nightwatch',
'Checking project title is set to nightwatch'
);
},
after : function(client) {
client.end();
}
};
Clean syntax
Selenium server
CSS & Xpath support
Easy to extend
Built-in test runner
Cloud services support
Continous integration support
PhantomJS
Maintained and developed much slower since mid 2017,
although it is still somewhat maintained.
First of all because it is much more mature and has
many great guides and tools.
It also used by many useful tools like CasperJS.
It uses older WebKit so it can simulate older Chrome browsers.
Also, as mentioned before, Phantom supports extensions like
Flash as opposed to Headless Chrome.
It uses Electron which is similar to Phantom but uses
a newer Chromium and actively maintained and developed,
since Electron’s main purpose is to build cross platform
desktop apps with JavaScript, HTML, and CSS.
yield Nightmare()
.goto('http://yahoo.com')
.type('input[title="Search"]', 'github nightmare')
.click('.searchsubmit')
phantom.create(function (ph) {
ph.createPage(function (page) {
page.open('http://yahoo.com', function (status) {
page.evaluate(
function () {
var el = document.querySelector('input[title="Search"]')
el.value = 'github nightmare'
},
function (result) {
page.evaluate(
function () {
var el = document.querySelector('.searchsubmit')
var event = document.createEvent('MouseEvent')
event.initEvent('click', true, false)
el.dispatchEvent(event)
},
function (result) {
ph.exit()
}
) // page.evaluate
}
) // page.evaluate
}) // page.open
}) // ph.createPage
}) // phantom.create
CasperJS
Written on top of PhantomJS and SlimerJS
Slimer was in widespread use for a long time although
considered experimental, but in the end of 2017
they released their beta version
Casper would probably migrate from PhantomJS to Puppeteer in the near future with their expected release of version 2.0
CasperJS exmple
var casper = require('casper').create();
casper.start('http://casperjs.org/');
casper.then(function() {
this.echo('First Page: ' + this.getTitle());
});
casper.thenOpen('http://phantomjs.org', function() {
this.echo('Second Page: ' + this.getTitle());
});
casper.run();
casper.test.begin('Hello, Test!', 1, function(test) {
test.assert(true);
test.done();
});
What's good pattern?
Writing, and rewriting, and rewriting, and rewriting, and testing different solutions:)
Testing components react PLAYGROUND
Testing components angular PLAYGROUND
Materials
- https://www.xfive.co/blog/testing-angular-faster-jest/
- https://angular.io/guide/testing
- https://codecraft.tv/courses/angular/unit-testing/components/
- http://plnkr.co/edit/N95Scj9LcUbxaLhZismT?p=preview
- https://codecraft.tv/courses/angular/unit-testing/jasmine-and-karma/
- https://github.com/dgarlitt/karma-nyan-reporter
- http://jsfiddle.net/wzAyL/1169/
- https://jsfiddle.net/opensas/3VuGs/
- https://facebook.github.io/jest/docs/en/tutorial-react.html
QUESTIONS
THANK YOU
#TESTING #JAVASCRIPT #FRONTEND
sebastian.sztyper@kotrak.com
Unit testing JS
By Sebastian Sztyper
Unit testing JS
- 317