"the process of being thorough, accurate, or exhaustive"
Profession-alism
Doing the right thing by your profession, rather than the right thing for yourself
Pair
Programming
Code
Reviews
Unit
Tests
Can also be
let unit = (input) => {
//...
return output;
};
Call a function with mock data and observe it's output
Like other forms of testing, unit tests measures success against criteria
Functions become... functional
const add = (a, b) => {
return a + b;
};
const myAlgo = (a, b) => {
//...
return result;
}
const pageLoad = () => {
A.init();
B.init();
// do all the things
};
const $ = () => {
return {
//all the stuff
}
}
dirty
A unit test should have the following properties:
When functions are unknown, they can be understood and approximated by modeling input to output values
Requirement
Iterative
Development
Test
Driven
Development
import cookieToObject from '../cookieToObject.js';
const getUserSettings = (cookie) => {
const data = cookieToObject(cookie);
return {
autoPlay: data.autoPlay ? data.autoPlay : false,
profileColor: data.profileColor ? data.profileColor : '#fff'
};
};
1. Examine Function
2. Describe using natural language
describe('getUserSettings', () => {
it('returns default data if nothing is passed in', () => {
data = getUserSettings();
assert.false(data.autoPlay);
assert.equal(data.profileColor, '#fff');
});
if('returns correct settings' => {
data = getUserSettings('autoPlay=true&profileColor=#00f');
assert.equal(data.profileColor, '#00f');
assert.true(data.autoPlay);
});
});
In these example
A framework which will execute and report tests, can run phantom.js, chrome, babel, browserify, etc
Libraries which you write the actual test code in. Supports should and assert syntax
Click loads
image flyout
Image caption loaded
from data attribute
Image flyout should be responsive & resized to fit container
Arrows should
navigate
through gallery
"x" should
close flyout
Large image
loaded from
data attribute
All that functionality & complexity boils down to:
DOM manipulation
Photo Gallery module example
let gallery = () => {
document.querySelector('.flyout__close').addEventListener('click', (evt) => {
let flyout = document.querySelector('.flyout');
flyout.parentNode.removeChild(flyout);
});
};
export default {
gallery
};
Photo Gallery test example
import {assert} from 'chai';
import {gallery} from './index.js';
describe('photoGallery', () => {
describe('close button', () => {
it('removes the drawer when clicked', (done) => {
});
});
});
let gallery = () => {
document.querySelector('.flyout__close').addEventListener('click', (evt) => {
let flyout = document.querySelector('.flyout');
flyout.parentNode.removeChild(flyout);
});
};
export default {
gallery
};
Refactor photoGallery function
import {assert} from 'chai';
import {gallery} from './index.js';
describe('photoGallery', () => {
describe('close button', () => {
it('removes the drawer when clicked', (done) => {
});
});
});
let gallery = ({context = document} = {}) => {
context.querySelector('.flyout__close').addEventListener('click', (evt) => {
let flyout = context.querySelector('.flyout');
flyout.parentNode.removeChild(flyout);
});
};
export default {
gallery
};
Pass context to module, default to `document`
Use `context` in place of document
Modify unit test
import {assert} from 'chai';
import {gallery} from './index.js';
let fragment = document.createElement('div');
fragment.innerHTML = `<div class="flyout">
<a class="flyout__close" href="#close-flyout">X Close</a>
</div>`;
describe('photoGallery', () => {
describe('close button', () => {
it('removes the drawer when clicked', (done) => {
gallery({context: fragment});
});
});
});
let gallery = ({context = document} = {}) => {
context.querySelector('.flyout__close').addEventListener('click', (evt) => {
let flyout = context.querySelector('.flyout');
flyout.parentNode.removeChild(flyout);
});
};
export default {
gallery
};
Mock the dom fragment
Pass the fragment
to thegallery function
Modify unit test
import {assert} from 'chai';
import {gallery} from './index.js';
let fragment = document.createElement('div');
fragment.innerHTML = `<div class="flyout">
<a class="flyout__close" href="#close-flyout">X Close</a>
</div>`;
describe('photoGallery', () => {
describe('close button', () => {
it('removes the drawer when clicked', () => {
gallery({context: fragment});
let closeBtn = fragment.querySelector('.flyout__close');
closeBtn.addEventListener('click', (evt) => {
assert.isFalse(!!fragment.querySelector('.flyout'));
})
closeBtn.dispatchEvent(new Event('click'));
});
});
});
export default function photoGallery({context = document} = {}){
context.querySelector('.flyout__close').addEventListener('click', (evt) => {
let flyout = context.querySelector('.flyout');
flyout.parentNode.removeChild(flyout);
});
};
Programmatically "click" on the close button
Verify that the `.flyout` is gone after the click
Modify unit test
let fragment document.createElement('div');
fragment.innerHTML = `<div class="flyout">
<a class="flyout__close" href="#close-flyout">X Close</a>
</div>`;
gallery = photoGallery({context: fragment});
describe('photoGallery', () => {
describe('close button', () => {
it('removes the drawer when clicked', (done) => {
closeBtn = fragment.querySelector('.flyout__close');
closeBtn.addEventListener('click', (evt) => {
assert.false(fragement.querySelector('.flyout'));
done();
})
closeBtn.dispatchEvent(new Event('click'));
});
});
});
let gallery = ({context = document} = {}) => {
context.querySelector('.flyout__close').addEventListener('click', (evt) => {
let flyout = context.querySelector('.flyout');
flyout.parentNode.removeChild(flyout);
});
};
export default {
gallery
};
Pass `done` to the test, then call it when done with an asynchronous operation
Taken a "dirty" function, abstracted the DOM, simulated user input, observed results
The close function can now be tested automatically, and a build can notify you if it fails
Problem: Inaccessible Functions
Solution: Expose Api
export default function init = () => {
inaccessible();
};
let function inaccessible = () => {
//...
};
let init = () => {
accessible();
};
let accessible = () => {
//...
};
export default {
init,
accessible
}
Problem: Direct `document` access
Solution: abstract the dom
let addClass = (selector, class) => {
[...document.querySelectorAll(selector)].forEach((elem) => {
elem.classList.add(class);
});
};
let addClass = ({
context = document,
selector,
class} = {}) => {
[...context.querySelectorAll(selector)].forEach((elem) => {
elem.classList.add(class);
});
};
Problem: Functions aren't discrete (anonymous functions)
Solution: Extract functionality into units
let myComplexFunction = (data) => {
//sort data
data.sort((a,b) => {
//...
});
//transform data
data = data.map((d)=>{
//...
});
return data;
};
let customSort = (a,b) => {
//...
};
let customMap = (elem) => {
//...
};
let myComplexFunction = (data) => {
//sort data
data.sort(customSort);
//transform data
data = data.map(customMap);
};