testing javascript in the frontend
Post on 13-Apr-2017
104 Views
Preview:
TRANSCRIPT
TDD Cycle - Rule #2
You must not write more of a
test than is sufficient to fail, or
fail to compile.
TDD Cycle - Rule #3
You must not write more
production code than is
sufficient to make the currently
failing test pass.
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
Uncaught ReferenceError: getMyObj is not defined
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
function getMyObj() {
}
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
function getMyObj() {
}
Uncaught TypeError: Cannot read property 'greet' of undefined
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
function getMyObj() {
return {};
}
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
function getMyObj() {
return {};
}
Uncaught TypeError: getMyObj(...).greet is not a function
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
function getMyObj() {
return {
greet: function() {
}
};
}
}
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
// production code
function getMyObj() {
return {
greet: function () {
}
};
}
✓
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
assert.equal(expected, actual);
// production code
function getMyObj() {
return {
greet: function() {
}
};
}
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
assert.equal(expected, actual);
// production code
function getMyObj() {
return {
greet: function() {
}
};
}
AssertionError: "Hello, Fred!" == undefined
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
assert.equal(expected, actual);
// production code
function getMyObj() {
return {
greet: function() {
return "Hello, Fred!";
}
};
}
TDD Cycle// tests
var expected = "Hello, Fred!";
var actual = getMyObj().greet("Fred");
assert.equal(expected, actual);
// production code
function getMyObj() {
return {
greet: function() {
return "Hello, Fred!";
}
};
}
✓
Why Testing ?- Increase code quality
- Deploy with confidence
- Self-explaining documentation
- Easy to refactor
What do we have to test ?- User interaction / events
- Manipulating the DOM
- Client-side business logic
- Ajax request (retrieve/send data)
My favorite testing stack:- Mochajs https://mochajs.org/
- Chaijs http://chaijs.com/
- Sinonjs: http://sinonjs.org/
- My browser (+ livereload)
User interaction / events// tests
it('should initialize a datepicker with
a given element', function() {
var input =
document.createElement('input');
DatePicker.init(input);
});
// production code
window.DatePicker = {
init: function() {}
}
User interaction / events// tests
it('should initialize a datepicker with
a given element', function() {
var input =
document.createElement('input');
DatePicker.init(input);
});
// production code
window.DatePicker = {
init: function() {}
}
✓
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
});
// production code
window.DatePicker = {
init: function() {}
}
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
var input =
document.createElement('input');
DatePicker.init(input);
});
// production code
window.DatePicker = {
init: function() {}
}
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
var input =
document.createElement('input');
DatePicker.init(input);
});
// production code
window.DatePicker = {
init: function() {}
}
✓
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
});
// production code
window.DatePicker = {
init: function() {}
}
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
});
// production code
window.DatePicker = {
init: function() {}
}
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
});
// production code
window.DatePicker = {
init: function() {}
}
✓
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
var element =
document.getElementById('datePicker');
expect(element).to.be.ok;
});
// production code
window.DatePicker = {
init: function() {}
}
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
var element =
document.getElementById('datePicker');
expect(element).to.be.ok;
});
// production code
window.DatePicker = {
init: function() {}
}
AssertionError: expected null to be truthy
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
var element =
document.getElementById('datePicker');
expect(element).to.be.ok;
});
// production code
window.DatePicker = {
init: function(input) {
[...]
}
}
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
var element =
document.getElementById('datePicker');
expect(element).to.be.ok;
});
// production code
[...]
input.addEventListener('focus',
function() {
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
User interaction / events// tests
it('should show a datepicker on input
focus', function() {
[...]
var event = new Event('focus');
input.dispatchEvent(event);
var element =
document.getElementById('datePicker');
expect(element).to.be.ok;
});
// production code
[...]
input.addEventListener('focus',
function() {
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
✓
User interaction / events// tests
it('should stop event propagation',
function() {
});
// production code
[...]
input.addEventListener('focus',
function() {
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
User interaction / events// tests
it('should stop event propagation',
function() {
[...]
var event = new Event('focus');
var spy = sinon.spy(event,
'stopPropagation');
input.dispatchEvent(event);
expect(spy).to.have.been.calledOnce;
});
// production code
[...]
input.addEventListener('focus',
function() {
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
User interaction / events// tests
it('should stop event propagation',
function() {
[...]
var event = new Event('focus');
var spy = sinon.spy(event,
'stopPropagation');
input.dispatchEvent(event);
expect(spy).to.have.been.calledOnce;
});
// production code
[...]
input.addEventListener('focus',
function() {
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
expected stopPropagation to have been called exactly once ...
User interaction / events// tests
it('should stop event propagation',
function() {
[...]
var event = new Event('focus');
var spy = sinon.spy(event,
'stopPropagation');
input.dispatchEvent(event);
expect(spy).to.have.been.calledOnce;
});
// production code
[...]
input.addEventListener('focus',
function(e) {
e.stopPropagation();
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
User interaction / events// tests
it('should stop event propagation',
function() {
[...]
var event = new Event('focus');
var spy = sinon.spy(event,
'stopPropagation');
input.dispatchEvent(event);
expect(spy).to.have.been.calledOnce;
});
// production code
[...]
input.addEventListener('focus',
function(e) {
e.stopPropagation();
var element =
document.createElement('div');
element.id = 'datePicker';
document.body.appendChild(element);
}
[...]
✓
Manipulating the DOM// tests
it('should add class "selected" on a day when it is clicked',
function(){
});
Manipulating the DOM// tests
it('should add class "selected" on a day when it is clicked',
function(){
[...]
var day = document.querySelector('#datePicker .day');
day.click();
});
Manipulating the DOM// tests
it('adds class "selected" on a day when clicked', function() {
[...]
var day = document.querySelector('#datePicker .day');
day.click();
var i = day.className.indexOf('selected');
expect(i > -1).to.be.true;
});
Mocking Time// tests
it('should hide datePicker after 300ms', function(){
[...]
var clock = sinon.useFakeTimers();
day.click();
clock.tick(299);
expect(datePicker.className.indexOf('hidden') > -1).to.be.false;
clock.tick(2);
expect(datePicker.className.indexOf('hidden') > -1).to.be.true;
clock.restore();
});
Mocking Browser methods// tests
beforeEach(function() {
this.dateNowStub = sinon.stub(Date, 'now', function() {
return new Date('2016-01-13').getTime();
});
});
afterEach(function(){
this.dateNowStub.restore();
});
Take it to the next levelHeadless browser testing (phantomjs, zombie, electron...)
Continuous Integration: automate your tests in jenkins, travis…
A few more tips- Find a BUG ? Write a TEST!
- Do TDD while PAIRING
- Use the Web APIs in your TESTS
- Don’t try to test the browser
- You don’t need to test libraries
top related