Slide 1

Slide 1 text

TDD with AngularJS Michael Tierney NationJS November 8, 2014

Slide 2

Slide 2 text

About me Director of UI Engineering at ( ) Co - organizer of Refresh Seattle Find me on both and as @ miketierney Intridea http :// intridea .com GitHub Twitter

Slide 3

Slide 3 text

Resources for Today’s talk: Code Samples : Slides : https :// github .com / miketierney / tdd - with - angular https :// speakerdeck .com / miketierney / tdd - with - angularjs

Slide 4

Slide 4 text

Why TDD? Added stability Improves inter - developer communication Makes maintenance easier

Slide 5

Slide 5 text

Added Stability Can make changes with relative confidence that they won ’ t break something without you knowing Encourages you to consider how you are implementing your code Encourages loosely coupled modules

Slide 6

Slide 6 text

Improved Communication Tests function as both documentation and API definition Impacts of changes can be more readily spotted in git di ffs , code review , etc .

Slide 7

Slide 7 text

Maintenance Because behavior is defined in the tests , understanding what the code does is easier for other contributors

Slide 8

Slide 8 text

Today’s Tools Angular Karma ( unit tests ) Protractor ( E 2E , or “ end to end ”) Jasmine PhantomJS Gulp ( not required , but helpful )

Slide 9

Slide 9 text

Today’s project Build a ToDo app using TDD .

Slide 10

Slide 10 text

Foundation Bootstrap the NPM and Bower files $ c d ~ / P r o j e c t s / t d d - w i t h - a n g u l a r j s $ e c h o " { } " > > p a c k a g e . j s o n $ e c h o ' { " n a m e " : " T D D w i t h A n g u l a r " } ' > > b o w e r . j s o n

Slide 11

Slide 11 text

Install Packages $ n p m i n s t a l l - - s a v e - d e v k a r m a g u l p $ b o w e r i n s t a l l - - s a v e a n g u l a r a n g u l a r - m o c k s a n g u l a r - r e s o u r c e

Slide 12

Slide 12 text

Con gure Karma $ n o d e _ m o d u l e s / k a r m a / b i n / k a r m a i n i t k a r m a . c o n f . j s … and just follow the on - screen instructions . Accept all of the defaults , and add both Chrome and PhantomJS as the browsers .

Slide 13

Slide 13 text

Con gure Karma (cont’d)

Slide 14

Slide 14 text

Con gure Karma (cont’d) Add the bower components to Karma : / / k a r m a . c o n f . j s / / l i s t o f f i l e s / p a t t e r n s t o l o a d i n t h e b r o w s e r f i l e s : [ ' b o w e r _ c o m p o n e n t s / a n g u l a r / a n g u l a r . m i n . j s ' , ' b o w e r _ c o m p o n e n t s / a n g u l a r - m o c k s / a n g u l a r - m o c k s . j s ' , ' b o w e r _ c o m p o n e n t s / a n g u l a r - r e s o u r c e / a n g u l a r - r e s o u r c e . j s ' , ' j s / * . j s ' , ' t e s t / * * / * _ s p e c . j s ' ] ,

Slide 15

Slide 15 text

Con gure Gulp / / g u l p f i l e . j s v a r g u l p = r e q u i r e ( ' g u l p ' ) , k a r m a = r e q u i r e ( ' k a r m a ' ) . s e r v e r ; / * * * R u n t e s t o n c e a n d e x i t * / g u l p . t a s k ( ' t e s t ' , f u n c t i o n ( d o n e ) { k a r m a . s t a r t ( { c o n f i g F i l e : _ _ d i r n a m e + ' / k a r m a . c o n f . j s ' , s i n g l e R u n : t r u e } , d o n e ) ; } ) ;

Slide 16

Slide 16 text

Create the directories $ m k d i r j s t e s t

Slide 17

Slide 17 text

index.html < ! D O C T Y P E h t m l > < h t m l l a n g = " e n " > < h e a d > < m e t a c h a r s e t = " U T F - 8 " / > < t i t l e > T o D o T D D < / t i t l e > < / h e a d > < b o d y n g - a p p = " t o d o A p p " > < s c r i p t s r c = " / b o w e r _ c o m p o n e n t s / a n g u l a r / a n g u l a r . m i n . j s " > < / s c r i p t > < s c r i p t s r c = " / b o w e r _ c o m p o n e n t s / a n g u l a r - r e s o u r c e / a n g u l a r - r e s o u r c e . m i < s c r i p t s r c = " / j s / a p p . j s " > < / s c r i p t > < / b o d y > < / h t m l >

Slide 18

Slide 18 text

js/app.js ' u s e s t r i c t ' ; a n g u l a r . m o d u l e ( ' t o d o A p p ' , [ ] ) ; Nothin ’ fancy yet ; just a bare minimum app .

Slide 19

Slide 19 text

Application specs It should display a list of to do items It should allow a user to add new to do items It should allow a user to toggle the item state It should allow a user to delete an item It should store the To Do items on a remote server

Slide 20

Slide 20 text

Let’s get going! Set up the rst test. / / t e s t / c o n t r o l l e r . j s ' u s e s t r i c t ' ; d e s c r i b e ( ' T o D o C o n t r o l l e r ' , f u n c t i o n ( ) { b e f o r e E a c h ( f u n c t i o n ( ) { v a r c o n t r o l l e r , s c o p e ; m o d u l e ( ' t o d o A p p . c o n t r o l l e r s ' ) ;

Slide 21

Slide 21 text

…a little more setup… i n j e c t ( f u n c t i o n ( $ c o n t r o l l e r , $ r o o t S c o p e ) { c o n t r o l l e r = $ c o n t r o l l e r ; s c o p e = $ r o o t S c o p e . $ n e w ( ) ; } ) ; t h i s . c r e a t e C o n t r o l l e r = f u n c t i o n ( ) { r e t u r n c o n t r o l l e r ( ' T o D o A p p C o n t r o l l e r ' , { $ s c o p e : s c o p e } ) ; } ; t h i s . c t r l = t h i s . c r e a t e C o n t r o l l e r ( ) ; } ) ; / / e n d o f b e f o r e E a c h ( )

Slide 22

Slide 22 text

Finally , a test! i t ( ' s h o u l d h a v e a t o d o s a r r a y ' , f u n c t i o n ( ) { e x p e c t ( t h i s . c t r l . t o d o I t e m s ) . t o E q u a l ( [ ] ) ; } ) ; } ) ; / / e n d o f D e s c r i b e ( ' T o D o C o n t r o l l e r ' )

Slide 23

Slide 23 text

Jasmine Matchers e x p e c t ( a ) . t o B e ( b ) ; e x p e c t ( a ) . n o t . t o B e ( n u l l ) ; e x p e c t ( m e s s a g e ) . t o M a t c h ( / b a r / ) ; e x p e c t ( n u l l ) . t o B e N u l l ( ) ; e x p e c t ( a . f o o ) . t o b e D e f i n e d ( ) ; e x p e c t ( a . b a r ) . t o B e U n d e f i n e d ( ) ; e x p e c t ( f o o ) . t o B e T r u t h y ( ) ; e x p e c t ( a ) . t o B e F a l s y ( ) ; e x p e c t ( [ ' f o o ' , ' b a r ' , ' b a z ' ] ) . t o C o n t a i n ( ' b a r ' ) ; e x p e c t ( b a r ) . t o T h r o w ( ) ;

Slide 24

Slide 24 text

Run the test $ g u l p t e s t

Slide 25

Slide 25 text

Aaaaaand fail

Slide 26

Slide 26 text

Create the controller / / j s / a p p . j s ' u s e s t r i c t ' ; a n g u l a r . m o d u l e ( ' t o d o A p p . c o n t r o l l e r s ' , [ ] ) . c o n t r o l l e r ( ' T o D o A p p C o n t r o l l e r ' , f u n c t i o n ( $ s c o p e ) { } ) ; a n g u l a r . m o d u l e ( ' t o d o A p p ' , [ ' t o d o A p p . c o n t r o l l e r s ' ] ) ;

Slide 27

Slide 27 text

Still failing But failing di fferently !

Slide 28

Slide 28 text

One more adjustment… a n g u l a r . m o d u l e ( ' t o d o A p p . c o n t r o l l e r s ' , [ ] ) . c o n t r o l l e r ( ' T o D o A p p C o n t r o l l e r ' , f u n c t i o n ( $ s c o p e ) { t h i s . t o d o I t e m s = [ ] ; } ) ;

Slide 29

Slide 29 text

Look ma, all green!

Slide 30

Slide 30 text

Add some functionality We need add and edit abilities . Let ’ s start with add .

Slide 31

Slide 31 text

Create the test… The basic requirement of a d d I t e m is that it add a new i t e m to the t o d o I t e m s Array . / / i n t e s t / c o n t r o l l e r . j s , a f t e r t h e f i r s t " i t ( ) " s t a t e m e n t d e s c r i b e ( ' c t r l . a d d I t e m ' , f u n c t i o n ( ) { i t ( ' s h o u l d a d d a n e w i t e m t o t o d o I t e m s ' , f u n c t i o n ( ) { e x p e c t ( t h i s . c t r l . t o d o I t e m s . l e n g t h ) . t o B e ( 0 ) ; t h i s . c t r l . a d d I t e m ( { n a m e : ' N e w T o D o I t e m ' } ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s ) . t o E q u a l ( [ { n a m e : ' N e w T o D o I t e m ' } ] ) } ) ; } ) ;

Slide 32

Slide 32 text

Add the new function… t h i s . a d d I t e m = f u n c t i o n ( n e w I t e m ) { t h i s . t o d o I t e m s . p u s h ( n e w I t e m ) ; } ; And we ’ re green again .

Slide 33

Slide 33 text

Do the same for Edit… i t ( ' s h o u l d t o g g l e t h e i t e m s t a t e ' , f u n c t i o n ( ) { v a r t o g g l e C o m p l e t e = t h i s . c t r l . t o g g l e C o m p l e t e ; t h i s . c t r l . t o d o I t e m s . f o r E a c h ( f u n c t i o n ( i t e m ) { t o g g l e C o m p l e t e ( i t e m ) ; } ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s [ 0 ] . c o m p l e t e ) . t o B e T r u t h y ( ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s [ 1 ] . c o m p l e t e ) . t o B e F a l s y ( ) ; t h i s . c t r l . t o d o I t e m s . f o r E a c h ( f u n c t i o n ( i t e m ) { t o g g l e C o m p l e t e ( i t e m ) ; } ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s [ 0 ] . c o m p l e t e ) . t o B e F a l s y ( ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s [ 1 ] . c o m p l e t e ) . t o B e T r u t h y ( ) ; } ) ;

Slide 34

Slide 34 text

Edit is more complicated, so check other cases as well i t ( ' s h o u l d c r e a t e a s t a t e i f n o n e e x i s t s ' , f u n c t i o n ( ) { v a r i t e m s = t h i s . c t r l . t o d o I t e m s ; t h i s . c t r l . t o d o I t e m s . p u s h ( { n a m e : ' N o c o m p l e t e p r o p e r t y ' } ) ; v a r i t e m = i t e m s [ i t e m s . l e n g t h - 1 ] ; e x p e c t ( i t e m . c o m p l e t e ) . t o B e U n d e f i n e d ( ) ; t h i s . c t r l . t o g g l e C o m p l e t e ( i t e m ) ; e x p e c t ( i t e m . c o m p l e t e ) . t o B e T r u t h y ( ) ; } ) ; / / . . . o t h e r t e s t s . . .

Slide 35

Slide 35 text

t h i s . t o g g l e C o m p l e t e = f u n c t i o n ( i t e m ) { i t e m . c o m p l e t e = ! i t e m . c o m p l e t e ; } ;

Slide 36

Slide 36 text

…and Delete d e s c r i b e ( ' c t r l . d e l e t e I t e m ' , f u n c t i o n ( ) { i t ( ' s h o u l d r e m o v e t h e i t e m ' , f u n c t i o n ( ) { v a r i = 0 , a d d I t e m = t h i s . c t r l . a d d I t e m , i t e m s = [ { n a m e : ' F i r s t I t e m ' } , { n a m e : ' S e c o n d I t e m ' } ] ; w h i l e ( i < = 3 ) { a d d I t e m ( i t e m s [ i ] ) ; i + + ; } t h i s . c t r l . d e l e t e I t e m ( 1 ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s ) . t o E q u a l ( [ i t e m s [ 0 ] ] ) ; } ) ; } ) ;

Slide 37

Slide 37 text

t h i s . d e l e t e I t e m = f u n c t i o n ( i n d e x ) { c t r l . t o d o I t e m s . s p l i c e ( i n d e x , 1 ) ; } ;

Slide 38

Slide 38 text

Building the Service Using ngResource , this is fairly straightforward .

Slide 39

Slide 39 text

The test setup d e s c r i b e ( ' T o D o S e r v i c e ' , f u n c t i o n ( ) { b e f o r e E a c h ( f u n c t i o n ( ) { m o d u l e ( ' t o d o A p p . s e r v i c e s ' ) ; i n j e c t ( f u n c t i o n ( $ h t t p B a c k e n d , $ i n j e c t o r ) { t h i s . t o d o = $ i n j e c t o r . g e t ( ' T o D o ' ) ; t h i s . h t t p B a c k e n d = $ h t t p B a c k e n d ; } ) ; } ) ; } ) ;

Slide 40

Slide 40 text

A couple of tests / / I n t h a t d e s c r i b e b l o c k i t ( ' s h o u l d h a v e C R U D c l a s s m e t h o d s ' , f u n c t i o n ( ) { e x p e c t ( t h i s . t o d o . g e t ) . t o B e D e f i n e d ( ) ; / / f o r i n d i v i d u a l i t e m e x p e c t ( t h i s . t o d o . q u e r y ) . t o B e D e f i n e d ( ) ; / / f o r a l l i t e m s e x p e c t ( t h i s . t o d o . s a v e ) . t o B e D e f i n e d ( ) ; / / c r e a t e } ) ; i t ( ' s h o u l d h a v e C R U D i n s t a n c e m e t h o d s ' , f u n c t i o n ( ) { v a r t o d o = n e w t h i s . t o d o ( ) ; e x p e c t ( t o d o . $ s a v e ) . t o B e D e f i n e d ( ) ; / / u p d a t e e x p e c t ( t o d o . $ d e l e t e ) . t o B e D e f i n e d ( ) ; / / d e l e t e e x p e c t ( t o d o . $ r e m o v e ) . t o B e D e f i n e d ( ) ; / / a l i a s f o r d e l e t e } ) ;

Slide 41

Slide 41 text

Changes to the application a n g u l a r . m o d u l e ( ' t o d o A p p . s e r v i c e s ' , [ ' n g R e s o u r c e ' ] ) . f a c t o r y ( ' T o D o ' , f u n c t i o n ( $ r e s o u r c e ) { r e t u r n $ r e s o u r c e ( ' / t o d o s / a p i / : i d ' ) ; } ) ;

Slide 42

Slide 42 text

Using $httpBackend Provided by the ngMock module ( filename is angular - mocks ) Makes it so you don ’ t need a backend to run tests ( a good thing ) Will watch for requests and return responses

Slide 43

Slide 43 text

When do I need it? The most obvious is when you see errors that look like this : i t ( ' s h o u l d q u e r y f r o m / t o d o s / a p i ' , f u n c t i o n ( ) { t h i s . t o d o . q u e r y ( ) ; / / c a u s e d t h e e r r o r } ) ;

Slide 44

Slide 44 text

$httpBackend test i t ( ' s h o u l d q u e r y f r o m / t o d o s / a p i ' , f u n c t i o n ( ) { t h i s . h t t p B a c k e n d . e x p e c t G E T ( ' / t o d o s / a p i ' ) . r e s p o n d ( [ ] ) ; / / m u s t r e t u r n * s o m e t h i n g * t h i s . t o d o . q u e r y ( ) ; t h i s . h t t p B a c k e n d . f l u s h ( ) ; } ) ;

Slide 45

Slide 45 text

Integrate the service Time to hook everything together !

Slide 46

Slide 46 text

Update the test con g / / i n t e s t / c o n t r o l l e r _ s p e c . j s i n j e c t ( f u n c t i o n ( $ c o n t r o l l e r , $ r o o t S c o p e , $ h t t p B a c k e n d , $ i n j e c t o r ) { / / . . . o t h e r i n j e c t i o n s t h i s . t o d o B a c k e n d = $ i n j e c t o r . g e t ( ' T o D o ' ) ; t h i s . h t t p B a c k e n d = $ h t t p B a c k e n d ; } ) ;

Slide 47

Slide 47 text

Set up the test d e s c r i b e ( ' c t r l . a d d I t e m ' , f u n c t i o n ( ) { b e f o r e E a c h ( f u n c t i o n ( ) { t h i s . i t e m = { n a m e : ' N e w T o D o I t e m ' } ; s p y O n ( t h i s . t o d o B a c k e n d . p r o t o t y p e , ' $ s a v e ' ) . a n d . c a l l T h r o u g h ( ) ; t h i s . h t t p B a c k e n d . w h e n P O S T ( / t o d o s \ / a p i \ ? . * / ) . r e s p o n d ( t h i s . i t e m ) ; } ) ;

Slide 48

Slide 48 text

Test the service call i t ( ' s h o u l d s a v e t o t h e T o D o s e r v i c e ' , f u n c t i o n ( ) { t h i s . c t r l . a d d I t e m ( { n a m e : ' N e w T o D o I t e m ' } ) ; e x p e c t ( t h i s . t o d o B a c k e n d . p r o t o t y p e . $ s a v e ) . t o H a v e B e e n C a l l e d W i t h ( t h i s . i t e m , j a s m i n e . a n y ( F u n c t i o n ) ) ; } ) ;

Slide 49

Slide 49 text

Write the test i t ( ' s h o u l d s a v e t o t h e T o D o s e r v i c e ' , f u n c t i o n ( ) { e x p e c t ( t h i s . c t r l . t o d o I t e m s . l e n g t h ) . t o B e ( 0 ) ; t h i s . c t r l . a d d I t e m ( t h i s . i t e m ) ; t h i s . h t t p B a c k e n d . f l u s h ( ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s . l e n g t h ) . t o B e ( 1 ) ; e x p e c t ( t h i s . c t r l . t o d o I t e m s [ 0 ] . n a m e ) . t o B e ( t h i s . i t e m . n a m e ) ; } ) ; } ) ; / / e n d o f d e s c r i b e ( ' c t r l . a d d I t e m ' ) ;

Slide 50

Slide 50 text

Update the controller / / A t t h e t o p o f t h e c o n t r o l l e r a n g u l a r . m o d u l e ( ' t o d o A p p . c o n t r o l l e r s ' , [ ' t o d o A p p . s e r v i c e s ' ] ) . c o n t r o l l e r ( ' T o D o A p p C o n t r o l l e r ' , f u n c t i o n ( $ s c o p e , T o D o ) { The controller is dependent on the services , so we tell Angular about that , then load load the service

Slide 51

Slide 51 text

Update the method t h i s . a d d I t e m = f u n c t i o n ( n e w I t e m ) { v a r i t e m = n e w T o D o ( ) ; i t e m . $ s a v e ( n e w I t e m , f u n c t i o n ( ) { c t r l . t o d o I t e m s . p u s h ( i t e m ) ; } ) ; } ;

Slide 52

Slide 52 text

E2E with Protractor

Slide 53

Slide 53 text

Install Protractor $ n p m i n s t a l l - - s a v e - d e v p r o t r a c t o r $ n o d e _ m o d u l e s / p r o t r a c t o r / b i n / w e b d r i v e r - m a n a g e r u p d a t e $ m k d i r e 2 e

Slide 54

Slide 54 text

Con gure Protractor Create an e 2e .conf .js file and add e x p o r t s . c o n f i g = { s e l e n i u m A d d r e s s : ' h t t p : / / l o c a l h o s t : 4 4 4 4 / w d / h u b ' , s p e c s : [ ' e 2 e / s p e c . j s ' ] } ;

Slide 55

Slide 55 text

Add a test / / i n e 2 e / s p e c . j s d e s c r i b e ( ' h o m e p a g e ' , f u n c t i o n ( ) { i t ( ' s h o u l d h a v e a t i t l e ' , f u n c t i o n ( ) { b r o w s e r . g e t ( ' h t t p : / / l o c a l h o s t : 8 0 0 0 / i n d e x . h t m l ' ) ; e x p e c t ( b r o w s e r . g e t T i t l e ( ) ) . t o E q u a l ( ' T o D o T D D ' ) ; } ) ; } ) ;

Slide 56

Slide 56 text

Run the servers In multiple terminal windows ( yeah , it ’ s awkward ): $ n o d e _ m o d u l e s / p r o t r a c t o r / b i n / w e b d r i v e r - m a n a g e r s t a r t and # t h i s c a n b e r e p l a c e d b y a n y o t h e r s e r v e r , # b u t o u r a p p d o e s n ' t h a v e a w e b s e r v e r $ p y t h o n - m S i m p l e H T T P S e r v e r

Slide 57

Slide 57 text

If you feel like this … Don ’ t worry , you ’ re not alone .

Slide 58

Slide 58 text

Run the test ( nally) $ n o d e _ m o d u l e s / p r o t r a c t o r / b i n / p r o t r a c t o r e 2 e . c o n f . j s

Slide 59

Slide 59 text

Protractor usage Uses the existing test framework Has some additional helper methods and global variables : b r o w s e r p r o t r a c t o r e l e m e n t b y Can do DOM traversal

Slide 60

Slide 60 text

Resources by Evan Hahn Jasmine ’ s Documentation : Angular ’ s Tutorial : Protractor Tutorial : JavaScript T esting with Jasmine : JavaScript Behavior - Driven Development jasmine .github .io /2.0/ introduction .html https :// docs .angularjs .org / tutorial https :// github .com / angular / protractor / blob / master / docs / tutorial .md

Slide 61

Slide 61 text

Thank You Gracias Danke Merci 谢谢 ありがとう