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

Javascript Testing with Jasmine

Roy Yu
October 11, 2014

Javascript Testing with Jasmine

This presentation is prepared for SVCC on Javascript Testing with Jasmine. It basically goes through basic Jasmine feature and provide tips for developers when they decide to start testing.

Roy Yu

October 11, 2014
Tweet

More Decks by Roy Yu

Other Decks in Technology

Transcript

  1. WHY UNIT TEST ? It acts as a “free” documentation

    for your code, they provide example to other developers of how your code works.
  2. WHY UNIT TEST ? It reduces bugs in new or

    existing features. It tells you right away if you someone’s code.
  3. WHY UNIT TEST ? It force you to understand the

    features and promote communication between technical and non-technical group.
  4. WHY UNIT TEST ? It improves your software / feature

    design. Thinking how to make a software testable ahead of time will decrease the complexity of your code.
  5. WHY UNIT TEST ? It makes you feel you are

    a super star … until someone in your team starts doing the same thing
  6. JASMINE – VERY BASIC Jasmine is a behavior driven testing

    framework for testing javascript code. Note: Behavior driven development ( BDD ) emphasis on writing your spec against your feature behavior ( business requirement ) that are small and easy to read. •  Small: Your test just need to test one thing only. •  Easy to read: Your test should need only one sentence to describe what you are testing.
  7. FIRST TEST describe(‘Shark’, function() { it(“should know how to swim”,

    function() { var shark = new Shark(); expect(shark.swim).not.toBeUndefined(); }); }); // This is just an imaginary test
  8. SUITE describe(‘Shark’, function() { it(“should know how to swim”, function()

    { var shark = new Shark(); expect(shark.swim).not.toBeUndefined(); }); }); Note: Suite typically defines a component of your application, it could be either a class or a function. The first argument take a “string”, and second take a function.
  9. SPEC describe(‘Shark’, function() { it(“should know how to swim”, function()

    { var shark = new Shark(); expect(shark.swim).not.toBeUndefined(); }); }); Note: Inside suite are the ‘specs’. It is testing what a particular piece of your component or your code should do. Each suite can holds any number of specs.
  10. MATCHERS describe(‘Shark’, function() { it(“should know how to swim”, function()

    { var shark = new Shark(); expect(shark.swim).not.toBeUndefined(); }); }); Note: Matchers are for validating your specs passing or not. It takes the argument inside expect() and check to see if it satisfies the criteria in the matcher.
  11. JASMINE - BEFOREEACH describe(‘Shark’, function() { var shark; beforeEach(function() {

    // this will run for each spec ( setUp ) shark = new Shark(); }); it(“should know how to swim”, function() { expect(shark.swim).not.toBeUndefined(); }); it(“has a fin”, function() { // … }); it(“has sharp teeth”, function() { // … }); });
  12. AFTEREACH describe(‘Shark’, function() { var shark; beforeEach(function() { shark =

    new Shark(); }); afterEach(function() { // this will run after each spec ( tearDown ) shark = null; }); it(“has a fin”, function() { // … }); it(“has sharp teeth”, function() { // … }); });
  13. SKIP SPEC describe(‘Shark’, function() { xit(“should know how to swim”,

    function() { // this spec will be skipped by the test runner }); it(“has a fin”, function() { // this spec will still be run }); });
  14. SKIP SUITE xdescribe(‘Shark’, function() { it(“should know how to swim”,

    function() { // … }); it(“has a fin”, function() { // … }); }); The whole suite will be skipped by the test runner.
  15. NESTED SUITES describe(‘Shark’, function() { describe(“Sharks’ teeth”, function() { it(“blah

    …”, function() { // … }); }); describe(“Sharks’ skin”, function() { it(“another blah …”, function() { // … }); }); });
  16. IDENTICAL toBe expect(a).toEqual(b); expect(a).toBe(a); expect(a).toBe(b); // toBe checks for if

    same object. var a = {test:”abc”}; var b = {test:”abc”};
  17. YES / NO toBeTruthy / toBeFalsy expect(true).toBeTruthy(); expect(12).toBeTruthy(); expect(null).toBeFalsy(); expect(false).toBeFalsy();

    expect(“”).toBeTruthy(); expect(undefined).toBeTruthy(); Expect(“hello”).toBeFalsy();
  18. THROW ERROR toThrow expect(_a_func_that_throw_error()).toThrow(); // It takes a function and

    see if it throw error expect(function() { return “something”; }).toThrow(); var _a_func_that_throw_error = function() { throw new Error(); }
  19. RECAP Let’s do some exercise Read the following sentences …

    I learn today Jasmine is about … describe, it, using expect and matchers describe, it, using expect and matchers describe, it, using expect and matchers
  20. RECAP Well, not totally true in previous slide, but true

    by so far. describe(‘Shark’, function() { it(“should know how to swim”, function() { var shark = new Shark(); expect(shark.swim).not.toBeUndefined(); }); });
  21. BASE CLASS var Shark = function() {} Shark.prototype.greet = function(thing)

    { this.eat(thing); } Shark.prototype.eat = function(thing) { this.say(thing); } Shark.prototype.say = function(thing) { return thing + “ is so yummy”; }
  22. POPUP QUIZ In plain English, can you describe a “Shark”

    by this definition ? var Shark = function() {} Shark.prototype.greet = function(thing) { this.eat(thing); } Shark.prototype.eat = function(thing) { this.say(thing); } Shark.prototype.say = function(thing) { return thing + “ is so yummy”; }
  23. SPY BASIC describe(“Shark”, function() { it(‘eats whatever it greets’, function()

    { var shark = new Shark(); spyOn(shark, ‘greet’); shark.greet(‘duck’); expect(shark.eat) .toHaveBeenCalled(); } }); var Shark = function() {} Shark.prototype.greet = function(thing) { this.eat(thing); } Shark.prototype.eat = function(thing) { this.say(thing); } Shark.prototype.say = function(thing) { return thing + “ is so yummy”; }
  24. SPY BASIC With spy, you can do the following as

    well •  expect(shark.eat).toHaveBeenCalledWith(‘duck’) •  expect(shark.eat.callCount).toBe(1) You can also force a spy function to return stuff you defined •  spyOn(shark, ‘say’).andReturn(‘Meh …’); // Note that the first argument for spyOn is the object, second argument is the method in string format
  25. SPY BASIC Some catcha … By default, spy will replace

    the behavior of your spied function, some people illustrate it as “eating the function”. In order to use spy as well as delegate the spied function back to the actual implementation, you can do the following spyOn(shark, ‘eat’).andCallThrough();
  26. SPY BASIC describe(“Shark”, function() { it(‘eats whatever it greets’, function()

    { var shark = new Shark(); spyOn(shark, ‘say’); spyOn(shark, ‘eat’) .andCallThrough(); shark.greet(‘duck’); expect(shark.eat) .toHaveBeenCalled(); expect(shark.say) .toHaveBeenCalled(); }); var Shark = function() {} Shark.prototype.greet = function(thing) { this.eat(thing); } Shark.prototype.eat = function(thing) { this.say(thing); } Shark.prototype.say = function(thing) { return thing + “ is so yummy”; }
  27. RECAP In short, spy records its interaction with other objects

    in your test. For example, you can retrieve run time statistic on the spied function. •  How many times the function was called •  What was the value return to the caller •  How many parameters the function was called with
  28. RECAP There are more concepts to Jasmine when coming to

    test doubles. In javascript testing world, there are spies, stubs and mocks. They could look very similar, but their existence are for different use cases. We will introduce other library that make it easy to understand in reference section. But for now, learning what spy means in Jasmine will give you what you need to write test.
  29. ASYNC TESTING In javascript, there are times you may need

    to test ‘events’, ‘ajax calls’ type of asynchronous actions. For example : // Assume after shark finishing its meal, it will send a signal ( event ) out ? describe(‘Shark’, function() { it(‘should say something after finishing its meal’, function() { } });
  30. ASYNC TESTING var Shark = function() { this.on(‘finish_eating’, function(evt, payload)

    { this.say(payload.item); }, this); } Shark.prototype.eat = function(thing) { this.trigger(‘finish_eating’, {item:thing}); }
  31. ASYNC TESTING In Jasmine, there are a few functions called

    runs, waits and waitsFor to support running specs for testing async operation. The sequence usually looks like :- // code that starts the async process runs // wait for something happen or timeout waitsFor // after waitsFor done its job, the next runs block will execute runs
  32. ASYNC TESTING describe(“Shark”, function() { it(‘say something after finish eating’,

    function() { var shark = new Shark(); spyOn(shark, ‘say’); runs(function() { // start the async support shark.eat(‘duck’); }); waitsFor(function() { // wait for something happen and return true, or it will time out return shark.say.callCount == 1; }, “Shark should say something”, 500); runs(function() { // still inside the async support expects(shark.say.callCount).toBeGreaterThan(0); }); } });
  33. You just finish the basic features of Jasmine ~ Hopefully

    you still remember what you just learned :-)
  34. TDD TDD and BDD go together hands in hands, not

    really competitors. The emphasis of TDD is your test is ahead of your implementation and it follows the Red Green Refactor pattern. Red à you write test that fail Green à you write basic implementation to pass the test Refactor à write you real implementation based on requirement
  35. TDD But let’s face it It is just a theory

    And you don’t aim for 100% test coverage!!
  36. TDD But it’s still good to practice it At least

    there are some coverage to start with!!
  37. BDD BDD could be considered as an extension to TDD

    The emphasis is more on business case and the purpose of your work
  38. BDD It combines two different activities into one •  The

    creation of Unit Tests and implementations •  The creation of Functional Tests and features
  39. THINK TEST FIRST Try to ask yourself next time when

    you start writing your code à “How can I test it?, Is this function testable?” It will help yourself to minimize the complexity of your code. If your existing function / class is doing too many things at the same time, try to write some specs and it will help your refactoring. Remember, your function should just need to do one thing and do it good.
  40. COMMUNICATION Unit test is not just about Green light or

    Red light, it is also about communication. Make sure you are writing the right specs, and making the right assumption. Invest more time with your manager / product / lead to truly understand what you are implementing. Just as what mentioned in the first few slides, writing/ planning test promotes communication between technical and non technical group.
  41. CONVERT TEST SPECS When you receive your requirement from your

    manager / product, it’s already come with “expected behavior” inside the “user story”.
  42. REFACTOR LEGACY CODE If code are not developed with testable

    in mind, it should have a lot of dependencies, for example, talk to database, sending emails. One of the task of refactoring code be to testable is first breaking the dependencies. After that, you want to check if any of your methods are doing too much, and there is clear role of that function. You can call this step reducing complexity.
  43. WHAT TO TEST You should test thing that is going

    to last. For example, you won’t test the a particular flow that is under A/B testing, since it won’t last long. You should test your critical path that deliver business values. If you have bunch of getter and setter, some people will actually exclude that, because it doesn’t deliver real values to the business. The main guide line is to think and apply whenever it makes sense, there is no point to aim for 100% coverage.
  44. NO DEPENDENCIES As a frontend dev, your unit test is

    probably not done correctly if … 1.  You need to do a real ajax call 2.  You instantiate a class in your test that is not what you are testing 3.  Other external data source For all those dependencies, you need to stub it out or mock it out. Your unit test should be isolated on its own.
  45. REMEMBER Unit testing is about testing as a (isolated) unit.

    So for all other dependencies, you will need to create stub, mock, fakeServer or fixture!!