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

QUESTIONS

THANK YOU

#TESTING #JAVASCRIPT #FRONTEND

sebastian.sztyper@kotrak.com

Unit testing JS

By Sebastian Sztyper

Unit testing JS

  • 317