Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD with AngularJS

Mike Tierney
November 08, 2014

TDD with AngularJS

Talk given at NationJS 2014.

In this talk we will build a simple AngularJS application using the principles of Test Driven Development (TDD). While we will primarily focus on using Karma and Jasmine to create unit tests, we will also look at creating end-to-end (commonly referred to as E2E) integration tests using Protractor.

Mike Tierney

November 08, 2014
Tweet

More Decks by Mike Tierney

Other Decks in Programming

Transcript

  1. 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
  2. Resources for Today’s talk: Code Samples : Slides : https

    :// github .com / miketierney / tdd - with - angular https :// speakerdeck .com / miketierney / tdd - with - angularjs
  3. 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
  4. 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 .
  5. Maintenance Because behavior is defined in the tests , understanding

    what the code does is easier for other contributors
  6. Today’s Tools Angular Karma ( unit tests ) Protractor (

    E 2E , or “ end to end ”) Jasmine PhantomJS Gulp ( not required , but helpful )
  7. 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
  8. 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
  9. 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 .
  10. 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 ' ] ,
  11. 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 ) ; } ) ;
  12. 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 >
  13. 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 .
  14. 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
  15. 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 ' ) ;
  16. …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 ( )
  17. 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 ' )
  18. 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 ( ) ;
  19. 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 ' ] ) ;
  20. 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 = [ ] ; } ) ;
  21. 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 ' } ] ) } ) ; } ) ;
  22. 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 .
  23. 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 ( ) ; } ) ;
  24. 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 . . .
  25. 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 ; } ;
  26. …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 ] ] ) ; } ) ; } ) ;
  27. 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 ) ; } ;
  28. 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 ; } ) ; } ) ; } ) ;
  29. 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 } ) ;
  30. 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 ' ) ; } ) ;
  31. 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
  32. 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 } ) ;
  33. $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 ( ) ; } ) ;
  34. 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 ; } ) ;
  35. 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 ) ; } ) ;
  36. 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 ) ) ; } ) ;
  37. 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 ' ) ;
  38. 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
  39. 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 ) ; } ) ; } ;
  40. 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
  41. 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 ' ] } ;
  42. 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 ' ) ; } ) ; } ) ;
  43. 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
  44. 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
  45. 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
  46. 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