testable javascript
DESCRIPTION
Writing testable javascript preso given at YUIConf 2011 (11/3/11) - good times!!!TRANSCRIPT
“If today were the last day of my life, would I want to do what I am about to do today?”
KISS
Minimize Your Pain
•Write Small•Write Simple•Test Early•Test Often
Write Small – Write Simple
•Isolate code to test•Loosely couple dependencies•Be obvious•Optimize last
Test Early – Test Often
Test Now
Explicit Dependencies
How things can go wrong…
April 12, 2023
// Call x.flub with twice zfunction foo(z) {
var x = new X(); // Mmmmm tightly coupled…
return x.flub(z*2);}
// We want to test if x.flub was called with 14var back = foo(7);// But only have ‘back’ to test!
April 12, 2023
How can we test only our code?
// Call x.flub with twice zfunction foo(x, z) {
return x.flub(z*2);}
// Test x.flub got called with 14…var x = new MockX(), foo(x, 7);
If (x.called(‘flub’).with(14)) { // a winner!!}
Really see this in constructors…
April 12, 2023
// Ewww – not directly testablefunction MyObject() {
this.x = new X();}
MyObject.Prototpe.foo = function(z) {return this.x.flub(z*2);
}
var test = new MyObject(), back = test.foo(7) // cannot test what foo() did
Inject It - Constructor
April 12, 2023
// Yea! Testable!function MyObject(x) {
this.x = x;}MyObject.Prototpe.foo = function(z) {
return this.x.flub(z*2);}
var x = new MockX(), test = new MyObject(x);
test.foo(7);If (x.called(‘flub’, with(14)) { // We tested only our function!}
Inject It - Setter
April 12, 2023
// Yea! Testable!function MyObject() { }MyObject.prototpe.setX= function(x) { this.x = x;};MyObject.prototpe.foo = function(z) {
return this.x.flub(z*2);};var x = new MockX(), test = new MyObject();test.setX(x);test.foo(7);If (x.called(‘flub’, with(14)) { // We tested only our function!}
YUI
YUI Explicit Dependencies
April 12, 2023
YUI.add(‘myObject’, function(Y) { // Code you want to test Y.MyObject = function() { this.a = new Y.a(); this.b = new Y.b(); this.c = new Y.c(); };}, { requires: [ ‘a’, ‘b’, ‘c’ ] });
YUI.use(‘myObject’, function(Y) { new Y.MyObject();});
Injecting YUI3 Dependencies??
April 12, 2023
YUI.add(‘myObject’, function(Y) { //// Code you want to test Y.MyObject = function(a, b, c) { this.a = a; this.b = b; this.c = c; };});YUI.use(‘myObject’, ‘a’, ‘b’, ‘c’, function(Y) { // Prod new Y.MyObject(new Y.a(), new Y.b(), new Y.c());});YUI.use(‘myObject’, ‘test’, function(Y) { // Test new Y.MyObject(Y.Mock(), Y.Mock(), Y.Mock());});
Isolating Dependencies
April 12, 2023
<script src =“yui3.js”></script><script src =“a.mock.js”></script><script src =“b.mock.js”></script><script src =“c.mock.js”></script><script src =“myObject.js”></script>
<script>YUI.use(‘myObject’, function(Y) { new Y.MyObject();</script>
Injecting YUI3 Dependencies
April 12, 2023
YUI.add(‘myObject’, function(Y) { //// Code you want to test Y.MyObject = function(a, b, c) { this.a = a || new Y.a(); this.b = b || new Y.b(); this.c = c || new Y.c(); };}, { requires: [‘a’, ‘b’, ‘c’ ] });YUI.use(‘myObject’, function(Y) { // Prod new Y.MyObject();});YUI.use(‘myObject’, ‘test’, function(Y) { // Test new Y.MyObject(Y.Mock(), Y.Mock(), Y.Mock());});
Injecting YUI3 Dependencies
April 12, 2023
YUI.add(‘myObject’, function(Y) { //// Code you want to test Y.MyObject = function() { } Y.MyObject.prototype.setX = function(x) { this.x = x; };}, { requires: [‘a’, ‘b’, ‘c’ ] });YUI.use(‘myObject’, function(Y) { // Prod new Y.MyObject();});YUI.use(‘myObject’, ‘test’, function(Y) { // Test new Y.MyObject().setX(Y.Mock());});
Isolating Dependencies
YUI({ modules: { a: { fullpath: ’/a-mock.js'
} }}).use('test', ’myObject', 'console',
function(Y) { var obj = new Y.MyObject();});
April 12, 2023
Isolating Dependencies
YUI({ filter: mock:
{ 'searchExp': “\\.js", 'replaceStr': "-mock.js" }}).use('test', ’myObject', 'console',
function(Y) { var obj = new Y.MyObject();});
April 12, 2023
Mock Objects
Mock Object
YUI().add(’a', function(Y) { Y.A= function() { var me = Y.Mock(); Y.Mock.expect(me, { method: ”render",
args: [‘#toolbar’] }); return me; }}, '1.0.0' ,{requires:['test']});
April 12, 2023
Testing with Mocked Dependencies
YUI().add(’a', function(Y) { Y.A= function() { return Y.Mock(); };}, '1.0.0' ,{requires:['test']});YUI().use(‘a’, ‘test’, ‘myObject’, function(Y) { var a = new Y.A(); Y.Mock.expect(a, { method: ”render",
args: [‘#toolbar’] }); new MyObject(a).render(); //// verify a.render(‘#toolbar’) was called});
April 12, 2023
Implicit Dependencies
‘Hidden’ Dependencies
Private functions are ‘hidden’ dependencies
Cannot test by definition! Make public? Are they part of the public
API?? Mock them out?? How??
Refactor private function to be explicit dependencies
Minimize their use – they cannot be tested directly
Treated exactly like any other dependency
April 12, 2023
Private functions
April 12, 2023
YOU CANNOT TEST THEM!
Don’t forget browser dependencies!
YUI.add(‘myObject’, function(Y) { //// Code you want to test Y.MyObject = function(window) { this.window = window; }; myObject.prototype.fiddle = function(str) { return
window.escape(str + ‘ FOOBIE’); }; });
YUI.use(‘myObject’, function(Y) { var winMock = new WindowMock(), myObj = new myObject(winMock); myObj.fiddle(‘foobie’); // Check WindowMock!!}
April 12, 2023
Recap
Explicit Deps
Problem: Public dependenciesSymptom: Explicitly declared
dependenciesCure: • Eliminate tightly coupled code/use
injection• Pre-load mock’ed versions of public
dependencies
April 12, 2023
Private Deps
Problem: Private dependenciesSymptom: Private methods and
functionsCure: • Minimize private dependencies• Make public dependency• Use composition to pull in private stuff
April 12, 2023
Browser Deps
Problem: Browser dependenciesSymptom: ‘window’ or global scope
usageCure: • Eliminate tightly coupled code/use
injection• Pre-load mock’ed versions of public
dependencies
April 12, 2023
Unit Testing Principles & Practices
All about dependency management
Identify dependencies and mock them out
Do not take your environment for granted
April 12, 2023
“Don’t Be Foolish”
Resources
• https://github.com/zzo/JUTE• [email protected]• @zzoass• http://randomgoo.blogspot.com/
April 12, 2023