Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Testing Javascript with Jasmine

9,704 views

Published on

Slides from my Lonestar Ruby Conf 2011 presentation.

*** Video of presentation: http://confreaks.com/videos/2531-lsrc2011-testing-javascript-with-jasmine ***

Agenda:
- Briefly cover why you should unit test
- Discuss what Jasmine is and isn't
- Show syntax with comparisons to RSpec
- Jasmine with:
- Vanilla JavaScript
- Jasmine with jQuery
- Jasmine with Ruby (not Rails)
- Jasmine with Rails
- Evergreen
- capybara-webkit
- Where does CoffeeScript, node.js, etc. fit in?
- Other helpful libraries/Wrap-up

Published in: Technology

Testing Javascript with Jasmine

  1. 2. Testing JavaScript with Jasmine By: Tim Tyrrell
  2. 3. Tim Tyrrell @timtyrrell    
  3. 4. Agenda <ul><li>- Briefly cover why you should unit test </li></ul><ul><li>- Discuss what Jasmine is and isn't </li></ul><ul><li>- Show syntax with comparisons to RSpec </li></ul><ul><li>- Jasmine with: </li></ul><ul><li>    - Vanilla JavaScript </li></ul><ul><li>    - Jasmine with jQuery </li></ul><ul><li>    - Jasmine with Ruby (not Rails) </li></ul><ul><li>    - Jasmine with Rails </li></ul><ul><li>    - Evergreen </li></ul><ul><li>    - capybara-webkit </li></ul><ul><li>- Where does CoffeeScript, node.js, etc. fit in? </li></ul><ul><li>- Other helpful libraries/Wrap-up </li></ul>
  4. 5. Why are you talking about JavaScript at a  Ruby conference?
  5. 6. Why unit test JavaScript?
  6. 7. &quot;External tests don't help us with internal design&quot; @glv
  7. 8. &quot;There is only one way to go truly fast. Do the best job you can. Anything else is slower.&quot; @unclebobmartin http://twitter.com/#!/unclebobmartin/status/39438225869254656
  8. 10. What is Jasmine?
  9. 11. Why Jasmine?
  10. 12. Jasmine is not an integration testing framework.
  11. 13. RSpec vs. Jasmine: Structure #RSpec describe &quot;Calculate&quot; do      describe &quot;#add&quot; do          it &quot;should return the sum&quot; do              ...          end      end end //Jasmine describe &quot;Calculate&quot;, function(){      describe &quot;#Add&quot;, function(){          it &quot;should return the sum&quot;, function(){          ...          };      }); });
  12. 14. RSpec vs. Jasmine: Before/After #RSpec before(:each) do           @calc = Calculator.new  end    after(:each) do           @calc.reset  end //Jasmine var calc;  beforeEach(function(){     calc = new Calculator(); });    afterEach(function(){     calc.reset();  });
  13. 15. RSpec vs. Jasmine: Expectations # RSpec  it &quot;should return the sum&quot; do       calc = Calculator.new        calc.add(1,1).should == 2     calc.add(1,2).should_not == 2 #Use one expectation per 'it'! end   // Jasmine  it(&quot;should return the sum&quot;, function() {           var calc = new Calculator();       expect(calc.Add(1,1)).toEqual(2);     expect(calc.Add(1,2)).not.toEqual(2); //Use one expectation per 'it'!  });
  14. 16. Jasmine Matchers expect(x).toEqual(y)   expect(x).toBe(y);   expect(x).toMatch(pattern) expect(x).toBeDefined() expect(x).toBeNull();   expect(x).toBeTruthy(); expect(x).toBeFalsy(); expect(x).toContain(y); expect(x).toBeLessThan(y); expect(x).toBeGreaterThan(y); expect(fn).toThrow(e);
  15. 17. Custom Matchers <ul><li>#RSpec </li></ul><ul><li>Spec::Matchers.define :be_greater_than_zero do </li></ul><ul><li>  match do |actual| </li></ul><ul><li>    actual > 0 </li></ul><ul><li>  end </li></ul><ul><li>end </li></ul><ul><li>Spec::Matchers.define :be_zero_when_subtracting do </li></ul><ul><li>  match do |actual, number| </li></ul><ul><li>    (actual - number) == 0 </li></ul><ul><li>  end </li></ul><ul><li>end </li></ul>
  16. 18. Custom Matchers <ul><li>// Jasmine </li></ul><ul><li>beforeEach(function() { </li></ul><ul><li>  this.addMatchers({ </li></ul><ul><li>    toBeGreaterThanZero: function() { </li></ul><ul><li>      return this.actual > 0; </li></ul><ul><li>    } </li></ul><ul><li>  }), </li></ul><ul><li>  this.addMatchers({ </li></ul><ul><li>    toBeZeroWhenSubtracting: function(number) { </li></ul><ul><li>      return (this.actual - number) === 0 </li></ul><ul><li>    } </li></ul><ul><li>  }) </li></ul><ul><li>}); </li></ul>
  17. 19. Custom Matchers <ul><li>describe('Custom Matchers', function () { </li></ul><ul><li>  it('should be greater than 0', function () { </li></ul><ul><li>    expect(1).toBeGreaterThanZero(); </li></ul><ul><li>  }); </li></ul><ul><li>  it('should equal zero when subtracting two numbers', function() { </li></ul><ul><li>    expect(5).toBeZeroWhenSubtracting(5); </li></ul><ul><li>  }); </li></ul><ul><li>}); </li></ul>
  18. 20. RSpec vs. Jasmine: Stubbing # RSpec  it &quot;should add the numbers&quot; do       calc = Calculator.new       calc.stub!(:add).and_return(3)       calc.add(1,1).should == 3 end  // Jasmine  it(&quot;should add the numbers&quot;, function() {       var calc = new Calculator();       spyOn(calc, 'add').andReturn(3);      expect(calc.Add(1,1)).toEqual(3);  });
  19. 21. TDD? &quot; Red , Green , Refactor&quot;
  20. 22. Why do *I* like TDD?
  21. 23. Jasmine with &quot;vanilla&quot; JavaScript <ul><li>- Download &quot;Stand alone&quot;  </li></ul><ul><li>  </li></ul><ul><li>http://pivotal.github.com/jasmine/download.html </li></ul>
  22. 28. describe(&quot;Calculator&quot;, function() {    }); Spec for Calculator:
  23. 29. describe(&quot;Calculator&quot;, function() {   var calc;   beforeEach(function(){     calc = new Calculator();   }); }); Spec for Calculator:
  24. 30. describe(&quot;Calculator&quot;, function() {   var calc;   beforeEach(function(){     calc = new Calculator();   }); }); Spec for Calculator:
  25. 31. describe(&quot;Calculator&quot;, function() {   var calc;   beforeEach(function(){     calc = new Calculator();   });   it(&quot;should return the sum&quot;, function() {     expect(calc.Add(1,1)).toEqual(2);   }); }); Spec for Calculator:
  26. 32. Failing Test
  27. 33. Create the &quot;Calculator&quot; function <ul><li>function Calculator () { </li></ul><ul><li>} </li></ul>
  28. 34. One passing, one left to fix
  29. 35. Add the method <ul><li>function Calculator () {   this.Add = function(num1, num2){     return num1 + num2;   }; } </li></ul>
  30. 36. Passed!
  31. 37. Jasmine with Ruby(not Rails)
  32. 38. $ gem install jasmine
  33. 39. $ jasmine init
  34. 40. Generated folder structure
  35. 41. $ rake jasmine
  36. 42. http://localhost:8888
  37. 43. $ rake jasmine:ci
  38. 44. Jasmine with JQuery
  39. 45.      * toBe(jQuerySelector)     * toBeChecked()     * toBeEmpty()     * toBeHidden()     * toBeSelected()     * toBeVisible()     * toContain(jQuerySelector)     * toExist()     * toHaveAttr(attributeName, attributeValue)     * toHaveBeenTriggeredOn(selector)     * toHaveClass(className)     * toHaveData(key, value)     * toHaveHtml(string)     * toHaveId(id)     * toHaveText(string)     * toHaveValue(value)     * toBeDisabled() jasmine-jquery Matchers
  40. 46. &quot;A lightweight, easy to use Javascript <span> injector for radical Web Typography&quot;
  41. 47. @davatron5000
  42. 48. Lettering.js <h1 class=&quot;fancy_title&quot;>Some Title</h1> <script>   $(document).ready(function() {     $(&quot;.fancy_title&quot;).lettering();   }); </script>
  43. 49. Lettering.js results <ul><li><h1 class=&quot;fancy_title&quot;> </li></ul><ul><li>  <span class=&quot;char1&quot;>S</span> </li></ul><ul><li>  <span class=&quot;char2&quot;>o</span> </li></ul><ul><li>  <span class=&quot;char3&quot;>m</span> </li></ul><ul><li>  <span class=&quot;char4&quot;>e</span> </li></ul><ul><li>  <span class=&quot;char5&quot;></span> </li></ul><ul><li>  <span class=&quot;char6&quot;>T</span> </li></ul><ul><li>  <span class=&quot;char7&quot;>i</span> </li></ul><ul><li>  <span class=&quot;char8&quot;>t</span> </li></ul><ul><li>  <span class=&quot;char9&quot;>l</span> </li></ul><ul><li>  <span class=&quot;char10&quot;>e</span> </li></ul><ul><li></h1> </li></ul>
  44. 50. Standalone folder structure + more
  45. 51. <ul><li><!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; </li></ul><ul><li>  &quot;http://www.w3.org/TR/html4/loose.dtd&quot;> </li></ul><ul><li><html> </li></ul><ul><li><head> </li></ul><ul><li>  <title>Jasmine Test Runner</title> </li></ul><ul><li>  <link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;lib/jasmine-1.0.2/jasmine.css&quot;> </li></ul><ul><li>  <script type=&quot;text/javascript&quot; src=&quot;lib/jasmine-1.0.2/jasmine.js&quot;></script> </li></ul><ul><li>  <script type=&quot;text/javascript&quot; src=&quot;lib/jasmine-1.0.2/jasmine-html.js&quot;></script> </li></ul><ul><li>  <script type=&quot;text/javascript&quot; src=&quot;lib/jquery-1.6.1.min.js&quot;></script> </li></ul><ul><li>  <script type=&quot;text/javascript&quot; src=&quot;lib/jasmine-jquery-1.2.0.js&quot;></script> </li></ul><ul><li>  <script type=&quot;text/javascript&quot; src=&quot;lib/jquery.lettering-0.6.1.min.js&quot;></script> </li></ul><ul><li>  <script type=&quot;text/javascript&quot; src=&quot;spec/LetteringSpec.js&quot;></script> </li></ul><ul><li></head> </li></ul><ul><li><body> </li></ul><ul><li><script type=&quot;text/javascript&quot;> </li></ul><ul><li>  jasmine.getEnv().addReporter(new jasmine.TrivialReporter()); </li></ul><ul><li>  jasmine.getEnv().execute(); </li></ul><ul><li></script> </li></ul><ul><li></body> </li></ul><ul><li></html> </li></ul>
  46. 52. <ul><li>describe(&quot;Lettering&quot;, function(){ </li></ul><ul><li>  beforeEach(function(){ </li></ul><ul><li>    //loadFixtures('myfixture.html'); </li></ul><ul><li>    setFixtures('<h1 class=&quot;fancy_title&quot;>Some Title</h1>'); </li></ul><ul><li>    $('.fancy_title').lettering(); </li></ul><ul><li>  }); </li></ul><ul><li>}); </li></ul>
  47. 53. <ul><li>describe(&quot;Lettering&quot;, function(){ </li></ul><ul><li>  beforeEach(function(){ </li></ul><ul><li>    //loadFixtures('myfixture.html'); </li></ul><ul><li>    setFixtures('<h1 class=&quot;fancy_title&quot;>Some Title</h1>'); </li></ul><ul><li>    $('.fancy_title').lettering(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should be visible&quot;, function() { </li></ul><ul><li>    expect($('.fancy_title')).toBeVisible(); </li></ul><ul><li>  }); </li></ul><ul><li>}); </li></ul>
  48. 54. <ul><li>describe(&quot;Lettering&quot;, function(){ </li></ul><ul><li>  beforeEach(function(){ </li></ul><ul><li>    //loadFixtures('myfixture.html'); </li></ul><ul><li>    setFixtures('<h1 class=&quot;fancy_title&quot;>Some Title</h1>'); </li></ul><ul><li>    $('.fancy_title').lettering(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should be visible&quot;, function() { </li></ul><ul><li>    expect($('.fancy_title')).toBeVisible(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should have 10 spans&quot;, function(){ </li></ul><ul><li>    expect($('.fancy_title > span').length).toEqual(10); </li></ul><ul><li>  }); </li></ul><ul><li>}); </li></ul>
  49. 55. <ul><li>describe(&quot;Lettering&quot;, function(){ </li></ul><ul><li>  beforeEach(function(){ </li></ul><ul><li>    //loadFixtures('myfixture.html'); </li></ul><ul><li>    setFixtures('<h1 class=&quot;fancy_title&quot;>Some Title</h1>'); </li></ul><ul><li>    $('.fancy_title').lettering(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should be visible&quot;, function() { </li></ul><ul><li>    expect($('.fancy_title')).toBeVisible(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should have 10 spans&quot;, function(){ </li></ul><ul><li>    expect($('.fancy_title > span').length).toEqual(10); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should contain 'S' as the text of the first span&quot;, function(){ </li></ul><ul><li>    expect($('.fancy_title > span:first').text()).toEqual('S'); </li></ul><ul><li>  }); </li></ul><ul><li>}); </li></ul>
  50. 56. <ul><li>describe(&quot;Lettering&quot;, function(){ </li></ul><ul><li>  beforeEach(function(){ </li></ul><ul><li>    //loadFixtures('myfixture.html'); </li></ul><ul><li>    setFixtures('<h1 class=&quot;fancy_title&quot;>Some Title</h1>'); </li></ul><ul><li>    $('.fancy_title').lettering(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should be visible&quot;, function() { </li></ul><ul><li>    expect($('.fancy_title')).toBeVisible(); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should have 10 spans&quot;, function(){ </li></ul><ul><li>    expect($('.fancy_title > span').length).toEqual(10); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should contain 'S' as the text of the first span&quot;, function(){ </li></ul><ul><li>    expect($('.fancy_title > span:first').text()).toEqual('S'); </li></ul><ul><li>  }); </li></ul><ul><li>  it(&quot;should have a class of 'char1' on the first span&quot;, function(){ </li></ul><ul><li>    expect($('.fancy_title > span:first')).toHaveClass('char1'); </li></ul><ul><li>  }); </li></ul><ul><li>}); </li></ul>
  51. 57. Jasmine with Rails gem &quot;jasmine&quot;  $ bundle install $ jasmine init (optional) $ rake jasmine or  $ rake jasmine:ci http://localhost:8888
  52. 58. Generated Rails spec folder
  53. 59. Generated default javascript objects
  54. 60. $ rake jasmine 
  55. 62. $ gem install evergreen
  56. 63. gem install evergreen
  57. 64. Evergreen default folder paths  (not generated)
  58. 65. $ evergreen serve
  59. 66. $ evergreen run
  60. 67. Evergreen and Rails 3 <ul><li>gem 'evergreen', :require => evergreen/rails'localhost:3000/evergreen </li></ul><ul><li>rake spec:javascripts </li></ul>
  61. 68. What about CoffeeScript???
  62. 69. Installing CoffeeScript <ul><li>brew install node.js (then add to PATH, NODE_PATH) </li></ul><ul><li>curl http://npmjs.org/install.sh | sh </li></ul><ul><li>npm install -g coffee-script </li></ul>
  63. 70. Add  a &quot;_spec.coffee&quot; file
  64. 71. <ul><li>require('/calculator.js') </li></ul><ul><li>describe 'Calculator', -> </li></ul><ul><li>  beforeEach -> </li></ul><ul><li>    @calc = new Calculator </li></ul><ul><li>  it 'should return the sum', -> </li></ul><ul><li>    expect(@calc.Add(1,1)).toEqual(2) </li></ul>
  65. 72. <ul><li>require('/calculator.js') </li></ul><ul><li>describe 'Calculator', -> </li></ul><ul><li>  beforeEach -> </li></ul><ul><li>    @calc = new Calculator </li></ul><ul><li>  it 'should return the sum', -> </li></ul><ul><li>    expect(@calc.Add(1,1)).toEqual(2) </li></ul>
  66. 73. <ul><li>require('/calculator.js') </li></ul><ul><li>describe 'Calculator', -> </li></ul><ul><li>  beforeEach -> </li></ul><ul><li>    @calc = new Calculator </li></ul><ul><li>  it 'should return the sum', -> </li></ul><ul><li>    expect(@calc.Add(1,1)).toEqual(2) </li></ul>
  67. 74. #winning
  68. 76. Headless Browser Install <ul><li>- Download and install Qt 4.7+ binary package </li></ul><ul><li>- gem install capybara-webkit -v=1.0.0.beta4 </li></ul><ul><li>- create &quot;config/evergreen.rb&quot; </li></ul>
  69. 77. Configure Evergreen <ul><li>require 'capybara-webkit' </li></ul><ul><li>Evergreen.configure do |config| </li></ul><ul><li>  #config.driver = :selenium </li></ul><ul><li>  config.driver = :webkit </li></ul><ul><li>  config.public_dir = 'public' </li></ul><ul><li>  config.spec_dir = 'spec/javascripts' </li></ul><ul><li>end </li></ul>
  70. 78. Using a Headless Browser <ul><li>#selenium </li></ul><ul><li>Finished in 3.92 seconds </li></ul><ul><li>2 examples, 0 failures </li></ul><ul><li>#webkit </li></ul><ul><li>Finished in 1.05 seconds </li></ul><ul><li>2 examples, 0 failures </li></ul>
  71. 79. Node.js + Jasmine <ul><li>&quot;jasmine-node&quot; </li></ul>
  72. 80. Any other cool utilities?
  73. 81. Rosie <ul><li>&quot; Rosie is a factory for building JavaScript objects, mostly useful for setting up test data. It is inspired by  factory_girl . &quot; </li></ul><ul><li>https://github.com/bkeepers/rosie </li></ul>
  74. 82. Summer Breeze <ul><li>&quot; Summer Breeze is a tool for generating fixture DOM output for Jasmine JavaScript tests directly from your Rails Views with a simple DSL for specifying fixtures &quot; </li></ul><ul><li>https://github.com/noelrappin/summer_breeze </li></ul>
  75. 83. jasminerice <ul><li>&quot;Pain free coffeescript testing under Rails 3.1&quot; </li></ul><ul><li>https://github.com/bradphelan/jasminerice </li></ul>
  76. 84. http://try-jasmine.heroku.com
  77. 85. Wrap Up <ul><li>1. Unit testing is important </li></ul><ul><li>2. Javascript can and should be unit tested </li></ul><ul><li>3. Jasmine is easy to setup and use </li></ul>
  78. 87. Thank You! @timtyrrell Questions? Did I miss any cool plugins? http://bit.ly/jasmine-lsrc http://spkr8.com/t/7818
  79. 88. http://pivotal.github.com/jasmine/ https://github.com/jnicklas/evergreen https://github.com/velesin/jasmine-jquery https://github.com/thoughtbot/capybara-webkit http://obtiva.com/blog/112-javascript-specs-with-jasmine-a-primer-for-rubyists-part-1 http://obtiva.com/blog/119 http://daverupert.com/2010/09/lettering-js/ http://www.engineyard.com/university/screencasts/javascript-tests-with-jasmine http://blog.levid.com/?p=29 http://blog.carbonfive.com/2010/10/21/rspec-best-practices/ https://github.com/johnbintz/guard-jasmine-headless-webkit References:

×