ADVANCED QUNIT
FRONT-END JAVASCRIPT UNIT TESTING
Lars Thorup, ZeaLake / @larsthorup
WHO IS LARS THORUP
Software developer/architect
C#, C++ and JavaScript
Test Driven Development
Coach: Teaching agile and automated testing
Advisor: Assesses software projects and companies
Founder and CEO of ZeaLake
AGENDA
Unit tests gives quality feedback
Make them fast
Make them precise
Run thousands of unit tests in seconds
We will look at
Mocking techniques
Front-end specific testing patterns
Assuming knowledge about JavaScript and unit testing
QUNIT BASICS
m dl (u i. ac l tr ,{
o ue ' tl cl ua o'
s tp f nt o ( {
e u : u ci n )
t i. ac =nw Cl ua o (;
hs cl
e a cl tr )
}
};
)
t s( ml il ' fn to ( {
e t ' u tp y, u ci n )
e ul ti .a c ml il ( , 7 ,4 , ' *' ;
q a (h sc l. ut p y6 ) 2 6 7 )
};
)
MOCKING, SPYING AND STUBBING
WHAT IS HARD TO TEST IN JAVASCRIPT?
HOW TO TEST IN ISOLATION?
We want to test code in isolation
here the code is the 'keypress' event handler
and isolation means not invoking the getMatch() method
' ep es :f nt o (l m n, ee t {
k yr s ' uc in e ee t v n)
v rp te n= t i. lm n. a (;
a at r
h se e etvl )
p te n+ S rn .r m hr oe ee tc a Cd )
a t r = t i gf oC aC d (v n. hr oe ;
v rm th= t i.e Mt hp t en ;
a ac
hsgt a c( at r)
i ( ac){
f m th
e et pe e te al (;
vn .r vn Df u t)
t i . l m n .a ( a c )
hs ee etvl m th ;
}
}
MOCKING METHODS
Using SinonJS
We can mock the getMatch() method
decide how the mock should behave
verify that the mocked method was called correctly
s nn su (u oo p ee ' eM th )r tr s 'o iu ';
i o. t ba tC ml t, g ta c '. eu n( mn q e )
$ 'n m' .r ge ( .v n ( ky r s' {h r oe 1 9) )
(#a e )t ig r $Ee t 'e pe s , c aC d: 0 }; ;
o (u oo pe eg t ac .a ld i h' ')
k at C ml t. eM th c le Wt (m );
e ul $ ' nm '. a (,'o iu ' ;
q a( ( #a e) vl ) m nq e)
MOCKING GLOBAL FUNCTIONS
Global functions are properties of the window object
o eP pp f nt o (r ){
p no u : u ci n ul
v rp pp =w n o. pn ul ' ba k, ' ei al ';
a ou
id wo e (r , _l n' r sz b e)
p pp fc s)
o u .o u( ;
}
v rp pp
a ou;
s nn su (i dw ' pn ,fn to ( {
i o. t bw no , oe ' uc i n )
p pp ={
ou
f cs s nn sy )
ou : i o .p (
}
;
r tr p pp
e u n o u;
};
)
a tC ml t. pn o u( za ae c m)
u oo p ee oe Pp p ' e lk .o ';
o (i dw oe .a l di h' el k .o ' ' b ak,' ei a l' )
k wn o .p nc le Wt ( za ae cm , _l n' rs zb e) ;
o (o u. ou .a l d ' o u c a gd)
k pp p fc sc le , fc s hn e';
MOCKING CONSTRUCTORS
Constructors are functions
with t i being the object to construct
hs
t i. nu = nw wn o . u oo pe ei pt l mn ,{
h si p t
e i dw At Cm l t( nu Ee et
l sU l ti .p in . it r
i t r: hs o to sl sU l
};
)
t i. nu .o u ( ;
h si p tf cs )
s nn su (i dw ' uo o pe e, fn to ( {
i o. t bw no , At Cm lt ' u ci n )
t i. ou = sn ns y )
h s fc s
i o. p( ;
};
)
o (o m 'r ae ';
k fr , ce td )
e ul wn o. uo o pe ec lC u t 1;
q a( i dw At Cm lt . al on , )
v ra g =w no .u oo p ee fr ta lc l .r s
a rs
i dw A tC ml t. i sC l. al ag ;
o (r s0. s 'n m ')
k ag []i(# ae );
d eE ul ag [ ] { it r :' sm Ul };
e pq a (r s1 , ls Ul / o er ')
v ra tC ml t =w no . uo op ee fr t al cl .h s au ;
a u o op ee
id wA tC m lt .i sC l. a lt iV le
o (u oo pe ef c sc le )
k at C ml t. ou .a l d;
HOW TO AVOID WAITING?
We want the tests to be fast
So don't use Jasmine w i s o (
atFr)
But we often need to wait
For animations to complete
For AJAX responses to return
d ly ie f nt o ( {
e aH d : u ci n )
v rs l =t i;
a ef
hs
s ti eu (u c in( {
e T mo tf nt o )
s l. lm n .i e)
ef ee et hd ( ;
} t i. pi n. ie e a)
, h so to s hd Dl y;
}
MOCKING TIMERS
Use Sinon's mock clock
Tick the clock explicitly
Now the test completes in milliseconds
without waiting
s nn ue ae ie s )
i o. s Fk Tm r( ;
a tC ml t. ea H d( ;
u oo p ee dl yi e)
o (( #a e) i (: ii l' )
k $ ' n m' .s 'v sb e );
s nn co kt c( 0 )
i o. l c. ik 50 ;
o (( #a e) i (: id n) ;
k $ ' n m' .s 'h de ' )
MOCKING TIME
n w D t ( tends to return different values over time
e ae)
...actually, that's the whole point :)
But how do we test code that relies on that?
We cannot e u l a value that changes on every run
q a on
Instead, Sinon can mock the D t ( constructor!
ae)
s nn ue ae ie s )
i o. s Fk Tm r( ;
v rt e =n wD t( ;
a hn
e ae)
s nn co kt c( 2 0)
i o. l c. ik 40 0;
v rl tr = n wD t (;
a ae
e ae )
e ul lt rg ti e )- te .e T m( ,4 00;
q a( a e. eT m(
h ng ti e) 2 0)
MOCKING AJAX REQUESTS
To test in isolation
To vastly speed up the tests
Many options
can.fixture
Mockjax
Sinon
c nf xu e' gt a e' f nt o (r gn l rs od ih {
a .i t r( /e Nm s, u ci n oi ia , ep n Wt )
r so di h{ i t [ r ce '} ;
e p nW t( ls : 'a hl ] )
};
)
v ra tC ml t = n wA t Cm lt (# ae , {
a u o op ee
e uo op e e' nm '
l sU l 'g ta e'
i t r: /e N ms
};
)
s nn co kt c( a .i tr .e a )
i o. l c. ik cn fx u ed ly ;
r so di h5 0; / It r a s r e e rr
e pn W t( 0) / n en l ev r ro
DOM FIXTURES
Supply the markup required by the code
Automatically cleanup markup after every test
Built into QUnit as #qunit-fixture
$ 'i pt i=nm " '. pe do ' qn tf xu e)
( <n u d "a e >)a p nT (# ui -i t r' ;
a tC ml t =n wA tC m lt (# ae )
u oo p ee
e uo op ee ' nm ';
SPYING ON EVENTS
How do we test that an event was cancelled?
Spy on the preventDefault() method
' ep es :f nt o (l m n, ee t {
k yr s ' uc in e ee t v n)
v rp te n= t i. lm n. a ( +
a at r
h se e etvl )
Sr n .r mh ro ee e tc aC d)
t ig fo Ca C d( vn .h r oe ;
v rm th= t i.e Mt hp t en ;
a ac
hsgt a c( at r)
i(a c) {
f m th
e et pe e te al (;
vn .r vn Df u t)
t i . l m n .a ( a c )
hs ee etvl m th ;
}
}
v rk yr sE et = $ E et 'e p es ,{ hr oe 1 9)
a e p es vn
. v n (k yr s' c a Cd : 0} ;
s nn sy ky rs E et ' rv nD fu t)
i o. p (e pe sv n, p ee t ea l' ;
$'n m' .r ge ( ep es vn )
(#a e )t ig rk yr s Ee t;
o (e pe sv n. r vn Df ut c le )
k ky r sE et pe et e al .a ld ;
SIMULATING CSS TRANSITIONS
PARAMERIZED AND CONDITIONED TESTS
Some code is browser specific
maybe using a browser specific API
and might only be testable in that browser
Tests can be conditioned
Or iterated...
c ne c(
a .a h [
{ ec ' uc s' r so s: { it [x ] ,e pc e: [ x] ,
d s : s ce s , e p ne l s: ' '} x e td ' '}
{ ec ' al r' r so s: 5 0 e pc e :[ }
d s : f iu e, e p ne 0 , xe td ]
] fn t o ( cn r o {
, u ci n se ai )
t s(ls Ul o to ,' +s e ai .e c fn to ( {
e t'i tr p in
cn ro ds , uc i n )
c nf xu e 'g ta e' f nt o (r gn l rs o di h {
a. it r( /e N ms , uc in o ii a, e pn Wt )
r so d ih se ai . ep ne ;
ep nW t( c nr or so s)
};
)
d eE ul a tC ml t. p in .i t se ai .x e td ;
ep qa (u oo p ee ot os ls , cn r oe pc e)
};
)
};
)
LEAK DETECTION
DOM ELEMENT LEAKS
DOM Fixtures are cleaned up automatically
But sometimes code needs to go beyond the fixture,
appending to $('body'), e.g for overlays
That code should have a way to clean up those elements
And our tests should invoke that cleanup
And we can easily verify that this is always done
t ad w: fn to ( {
e ro n u ci n )
v rl as =$ ' oy )c id e (: o( qn tr p re ) )
a ek
(b d' . hl rn 'n t# u i- eo tr ' ;
e ul la sl n t, 0 'o DM ee et l ae ' ;
q a (e k. eg h , n O l mn s ek d )
l as rm v( ;
e k .e oe )
}
MEMORY LEAKS
window.performance.memory: a Google Chrome extension
run Chrome with --enable-memory-info --js-flags="--exposegc"
Collect memory consumption data for every test
Sort and investigate the largest memory consumers
However, performance data is not reproducible
And the garbage collector disturbs the picture
But still usable
s tp f nt o ( {
e u: u ci n )
w no .c )
i d wg (;
t i. ep ie e oe =w no. ef ra c. eo y ue JH aS z;
h s ha Sz Bf r
i d wp r o m n e m m r. s d S e p i e
}
,
t ad w: fn to ( {
e ro n u ci n )
w no .c )
i d wg (;
t i. ep ie f e = w no.e fr ac . eo yu eJ H aS z;
h s ha Sz At r
i dwp ro mn em mr . sd Se pi e
c no el gs e.e pi ef e -s e . e pi ee oe;
o s l. o( pcha S zA tr
pc ha Sz B fr)
}
RESOURCES
github.com/larsthorup/qunit-demo-advanced
@larsthorup
qunitjs.com
sinonjs.com
canjs.com
github.com/hakimel/reveal.js

Advanced QUnit - Front-End JavaScript Unit Testing

  • 1.
    ADVANCED QUNIT FRONT-END JAVASCRIPTUNIT TESTING Lars Thorup, ZeaLake / @larsthorup
  • 2.
    WHO IS LARSTHORUP Software developer/architect C#, C++ and JavaScript Test Driven Development Coach: Teaching agile and automated testing Advisor: Assesses software projects and companies Founder and CEO of ZeaLake
  • 3.
    AGENDA Unit tests givesquality feedback Make them fast Make them precise Run thousands of unit tests in seconds We will look at Mocking techniques Front-end specific testing patterns Assuming knowledge about JavaScript and unit testing
  • 4.
    QUNIT BASICS m dl(u i. ac l tr ,{ o ue ' tl cl ua o' s tp f nt o ( { e u : u ci n ) t i. ac =nw Cl ua o (; hs cl e a cl tr ) } }; ) t s( ml il ' fn to ( { e t ' u tp y, u ci n ) e ul ti .a c ml il ( , 7 ,4 , ' *' ; q a (h sc l. ut p y6 ) 2 6 7 ) }; )
  • 5.
  • 6.
    WHAT IS HARDTO TEST IN JAVASCRIPT?
  • 7.
    HOW TO TESTIN ISOLATION? We want to test code in isolation here the code is the 'keypress' event handler and isolation means not invoking the getMatch() method ' ep es :f nt o (l m n, ee t { k yr s ' uc in e ee t v n) v rp te n= t i. lm n. a (; a at r h se e etvl ) p te n+ S rn .r m hr oe ee tc a Cd ) a t r = t i gf oC aC d (v n. hr oe ; v rm th= t i.e Mt hp t en ; a ac hsgt a c( at r) i ( ac){ f m th e et pe e te al (; vn .r vn Df u t) t i . l m n .a ( a c ) hs ee etvl m th ; } }
  • 8.
    MOCKING METHODS Using SinonJS Wecan mock the getMatch() method decide how the mock should behave verify that the mocked method was called correctly s nn su (u oo p ee ' eM th )r tr s 'o iu '; i o. t ba tC ml t, g ta c '. eu n( mn q e ) $ 'n m' .r ge ( .v n ( ky r s' {h r oe 1 9) ) (#a e )t ig r $Ee t 'e pe s , c aC d: 0 }; ; o (u oo pe eg t ac .a ld i h' ') k at C ml t. eM th c le Wt (m ); e ul $ ' nm '. a (,'o iu ' ; q a( ( #a e) vl ) m nq e)
  • 9.
    MOCKING GLOBAL FUNCTIONS Globalfunctions are properties of the window object o eP pp f nt o (r ){ p no u : u ci n ul v rp pp =w n o. pn ul ' ba k, ' ei al '; a ou id wo e (r , _l n' r sz b e) p pp fc s) o u .o u( ; } v rp pp a ou; s nn su (i dw ' pn ,fn to ( { i o. t bw no , oe ' uc i n ) p pp ={ ou f cs s nn sy ) ou : i o .p ( } ; r tr p pp e u n o u; }; ) a tC ml t. pn o u( za ae c m) u oo p ee oe Pp p ' e lk .o '; o (i dw oe .a l di h' el k .o ' ' b ak,' ei a l' ) k wn o .p nc le Wt ( za ae cm , _l n' rs zb e) ; o (o u. ou .a l d ' o u c a gd) k pp p fc sc le , fc s hn e';
  • 10.
    MOCKING CONSTRUCTORS Constructors arefunctions with t i being the object to construct hs t i. nu = nw wn o . u oo pe ei pt l mn ,{ h si p t e i dw At Cm l t( nu Ee et l sU l ti .p in . it r i t r: hs o to sl sU l }; ) t i. nu .o u ( ; h si p tf cs ) s nn su (i dw ' uo o pe e, fn to ( { i o. t bw no , At Cm lt ' u ci n ) t i. ou = sn ns y ) h s fc s i o. p( ; }; ) o (o m 'r ae '; k fr , ce td ) e ul wn o. uo o pe ec lC u t 1; q a( i dw At Cm lt . al on , ) v ra g =w no .u oo p ee fr ta lc l .r s a rs i dw A tC ml t. i sC l. al ag ; o (r s0. s 'n m ') k ag []i(# ae ); d eE ul ag [ ] { it r :' sm Ul }; e pq a (r s1 , ls Ul / o er ') v ra tC ml t =w no . uo op ee fr t al cl .h s au ; a u o op ee id wA tC m lt .i sC l. a lt iV le o (u oo pe ef c sc le ) k at C ml t. ou .a l d;
  • 11.
    HOW TO AVOIDWAITING? We want the tests to be fast So don't use Jasmine w i s o ( atFr) But we often need to wait For animations to complete For AJAX responses to return d ly ie f nt o ( { e aH d : u ci n ) v rs l =t i; a ef hs s ti eu (u c in( { e T mo tf nt o ) s l. lm n .i e) ef ee et hd ( ; } t i. pi n. ie e a) , h so to s hd Dl y; }
  • 12.
    MOCKING TIMERS Use Sinon'smock clock Tick the clock explicitly Now the test completes in milliseconds without waiting s nn ue ae ie s ) i o. s Fk Tm r( ; a tC ml t. ea H d( ; u oo p ee dl yi e) o (( #a e) i (: ii l' ) k $ ' n m' .s 'v sb e ); s nn co kt c( 0 ) i o. l c. ik 50 ; o (( #a e) i (: id n) ; k $ ' n m' .s 'h de ' )
  • 13.
    MOCKING TIME n wD t ( tends to return different values over time e ae) ...actually, that's the whole point :) But how do we test code that relies on that? We cannot e u l a value that changes on every run q a on Instead, Sinon can mock the D t ( constructor! ae) s nn ue ae ie s ) i o. s Fk Tm r( ; v rt e =n wD t( ; a hn e ae) s nn co kt c( 2 0) i o. l c. ik 40 0; v rl tr = n wD t (; a ae e ae ) e ul lt rg ti e )- te .e T m( ,4 00; q a( a e. eT m( h ng ti e) 2 0)
  • 14.
    MOCKING AJAX REQUESTS Totest in isolation To vastly speed up the tests Many options can.fixture Mockjax Sinon c nf xu e' gt a e' f nt o (r gn l rs od ih { a .i t r( /e Nm s, u ci n oi ia , ep n Wt ) r so di h{ i t [ r ce '} ; e p nW t( ls : 'a hl ] ) }; ) v ra tC ml t = n wA t Cm lt (# ae , { a u o op ee e uo op e e' nm ' l sU l 'g ta e' i t r: /e N ms }; ) s nn co kt c( a .i tr .e a ) i o. l c. ik cn fx u ed ly ; r so di h5 0; / It r a s r e e rr e pn W t( 0) / n en l ev r ro
  • 15.
    DOM FIXTURES Supply themarkup required by the code Automatically cleanup markup after every test Built into QUnit as #qunit-fixture $ 'i pt i=nm " '. pe do ' qn tf xu e) ( <n u d "a e >)a p nT (# ui -i t r' ; a tC ml t =n wA tC m lt (# ae ) u oo p ee e uo op ee ' nm ';
  • 16.
    SPYING ON EVENTS Howdo we test that an event was cancelled? Spy on the preventDefault() method ' ep es :f nt o (l m n, ee t { k yr s ' uc in e ee t v n) v rp te n= t i. lm n. a ( + a at r h se e etvl ) Sr n .r mh ro ee e tc aC d) t ig fo Ca C d( vn .h r oe ; v rm th= t i.e Mt hp t en ; a ac hsgt a c( at r) i(a c) { f m th e et pe e te al (; vn .r vn Df u t) t i . l m n .a ( a c ) hs ee etvl m th ; } } v rk yr sE et = $ E et 'e p es ,{ hr oe 1 9) a e p es vn . v n (k yr s' c a Cd : 0} ; s nn sy ky rs E et ' rv nD fu t) i o. p (e pe sv n, p ee t ea l' ; $'n m' .r ge ( ep es vn ) (#a e )t ig rk yr s Ee t; o (e pe sv n. r vn Df ut c le ) k ky r sE et pe et e al .a ld ;
  • 17.
  • 18.
    PARAMERIZED AND CONDITIONEDTESTS Some code is browser specific maybe using a browser specific API and might only be testable in that browser Tests can be conditioned Or iterated... c ne c( a .a h [ { ec ' uc s' r so s: { it [x ] ,e pc e: [ x] , d s : s ce s , e p ne l s: ' '} x e td ' '} { ec ' al r' r so s: 5 0 e pc e :[ } d s : f iu e, e p ne 0 , xe td ] ] fn t o ( cn r o { , u ci n se ai ) t s(ls Ul o to ,' +s e ai .e c fn to ( { e t'i tr p in cn ro ds , uc i n ) c nf xu e 'g ta e' f nt o (r gn l rs o di h { a. it r( /e N ms , uc in o ii a, e pn Wt ) r so d ih se ai . ep ne ; ep nW t( c nr or so s) }; ) d eE ul a tC ml t. p in .i t se ai .x e td ; ep qa (u oo p ee ot os ls , cn r oe pc e) }; ) }; )
  • 19.
  • 20.
    DOM ELEMENT LEAKS DOMFixtures are cleaned up automatically But sometimes code needs to go beyond the fixture, appending to $('body'), e.g for overlays That code should have a way to clean up those elements And our tests should invoke that cleanup And we can easily verify that this is always done t ad w: fn to ( { e ro n u ci n ) v rl as =$ ' oy )c id e (: o( qn tr p re ) ) a ek (b d' . hl rn 'n t# u i- eo tr ' ; e ul la sl n t, 0 'o DM ee et l ae ' ; q a (e k. eg h , n O l mn s ek d ) l as rm v( ; e k .e oe ) }
  • 21.
    MEMORY LEAKS window.performance.memory: aGoogle Chrome extension run Chrome with --enable-memory-info --js-flags="--exposegc" Collect memory consumption data for every test Sort and investigate the largest memory consumers However, performance data is not reproducible And the garbage collector disturbs the picture But still usable s tp f nt o ( { e u: u ci n ) w no .c ) i d wg (; t i. ep ie e oe =w no. ef ra c. eo y ue JH aS z; h s ha Sz Bf r i d wp r o m n e m m r. s d S e p i e } , t ad w: fn to ( { e ro n u ci n ) w no .c ) i d wg (; t i. ep ie f e = w no.e fr ac . eo yu eJ H aS z; h s ha Sz At r i dwp ro mn em mr . sd Se pi e c no el gs e.e pi ef e -s e . e pi ee oe; o s l. o( pcha S zA tr pc ha Sz B fr) }
  • 22.