Shaken, not Stirred – Using Spies in Jasmine



Shaken, not Stirred – Using Spies in Jasmine

0 0


jasmine-spies-slidedeck


On Github daniellecloss / jasmine-spies-slidedeck

Shaken, not Stirred

Using Spies in Jasmine

Created by Danielle Closs / @daniellecloss
1 of 16 Hello! I'm Danielle, and I am a UI Web developer at Arbor Networks. Sometimes I spy on my Javascript code, occasionally with a drink in hand, using Jasmine Spies.

What is aJasmine Spy?

2 of 16 So, you may be wondering what a Jasmine Spy is, or for that matter, what Jasmine is. Jasmine is a testing framework for Javascript....

A Jasmine Spy is...

...Jasmine's version of a mock or test double function.

It is generally used to "mock" a function or other javascript object, so that you can track a function's implementation.

3 of 16 ....and a Jasmine Spy is basically a mock function, also known as a test double function, that can be used to follow and track a function's implementation in your code. Generally speaking, you want to use Jasmine Spies when you want more control and tracking for your testing implementation.

Use Cases

  • Mock an API call or API return data, so that you can control the API's return data, useful if you don’t want to directly call your API during test running.
  • Mock methods from third-party libraries (like jQuery), useful when testing browser functionality.
4 of 16 Some common use cases for Jasmine Spies are: - mocking an API call or the return data, allowing controlled and consistent return data, independant of the API service - mocking jQuery functions, to test event handlers, like button clicks

You Can Mock API data?

Sure! And who wouldn't want that?

// part of the Jasmine tests
var Spy = require('../jasmine-spies.js');
Spy.api_data = {
  data : {
    'George' : '006',
    'Sue' : '008',
  },
  error : 'No knowledge of the existence of this one.',

Set up a new object, with a data property, in the "describe" block.

Setup

5 of 16 Mocking an API data return, that sounds pretty sweet, right? It is one of the many things you can do with Jasmine Spies. Let's walk through an example... To start mocking a data return for an API, you basically create a copy of the API data, in the format that you would normally expect back from your API, but in this case, you have full control of what is contained in this data. Then you don't have to worry about things like if the API is accessible during the tests.

The "tested" function

// this is the actual code you are testing
// recognize this guy here?? Look ma, no data! :)
Spy.api_data = {};
Spy.call_api = function(spy_name) { // testing this
    if(spy_name === '' ||
    	this.api_data.data[spy_name] === undefined) {
        return this.api_data.error;
    }
    var spy_data = this.api_data.data[spy_name];
    return spy_data;

Set up the function you are testing; ours is call_api().

Setup

6 of 16 This is the actual function that we are testing, the call_api function. I did shortcut this code a bit, to make it easier to understand for this talk. Normally you would have some kind of async implementation, but this should be enough for this example. Honestly, I can only fit so much on these slides, so use your imagination :) Notice the api_data property here, in our code to test, is an empty object...

Mock data in beforeEach()

// part of the Jasmine tests
beforeEach(function() {
  var api_return, api_data, api_error, newSpy;
  Spy.api_data = {
    data : {
      'George' : '006',
      'Sue' : '008',
    },
    error : 'No knowledge of the existence of this one.',

Using the beforeEach() block allows the api_data and the Jasmine spy to be accessible before each test.

Setup

7 of 16 ...and here, in our test suite, the api_data property has data in the object. Nice, huh? For our example, our Jasmine API mock data is placed in a beforeEach() block. When the mocked data or function is put in a beforeEach() block, it is accessible to each test in that test suite.

Create your spy

// part of the Jasmine tests
// this is the spy creation, we are spying
// on the actual call_api function implementation
spyOn(Spy, 'call_api');

This is where the magic happens!

Spying on our function allows us to track its implementation and usage, with special Jasmine Spy methods and matchers.

Setup

8 of 16 This is where we actually attach the spy implementation to our function call, using spyOn. The first param for the spyOn function is the actual object we are testing, and the second param, in quotes, is the method name. Yes, I did say, in quotes. Not sure why, but that's how it works!

Using .and.callThrough()

// part of the Jasmine tests
// use callThrough(), so you can follow through
// the full code implemention on the code chain
Spy.call_api.and.callThrough();

WARNING: Use and.callThrough() to follow through on the function calls (i.e.: delegate to the actual implementation), otherwise our tests could FAIL!

Setup

9 of 16 While I was creating this sample code, I ran into an issue where my tests were failing, because functions were not following through on their actual implementation. In this case, you need to use the and.callThrough() method, which allows your code to keep running through it's normal process. If you don't use this method and you have chained function execution or a function that needs to follow through, the code will stop processing, and your tests will fail.

Grab your "Spied On" returns

// part of the Jasmine tests
// this returns Sue's spy ID
api_return_sue = Spy.call_api('Sue');

// this call will return an error,
// since Fred doesn't exist in our data
api_return_fred = Spy.call_api('Fred');

Add the return data to variables,to use with matchers inside our tests.

Setup

10 of 16 To make things easier to test in the "it" blocks, I added the returns from the spied on function to variables that were accessible to the whole test suite, placed in the beforeEach() block. This way I could just assert against the variables, instead of passing values around. I also think it is a bit more readable!

Create Assertions with Matchers

// part of the Jasmine tests
it("tracks that it has been called", function() {
  expect(Spy.call_api).toHaveBeenCalled();
});

This Jasmine Spy matcher checks to see if this function has been called at least one time. It might be nice to do this before testing our mocked API data, just to be sure!

Testing

11 of 16 Now we can create some assertions, in the "it" blocks. Jasmine Spies have special matchers, like this one, toHaveBeenCalled(). All this matcher does is verify that the function has been called at least once.

Testing your "Mock" API data

it("when called, returns the requested value",
  function() {
    // recognize these variables?
    expect(api_return_sue).toEqual('008');
    expect(api_return_fred)
      .toEqual('No knowledge of the existence of this one.');
  });

After setting up our function, our API data, our tests, and our spy, we can use any Jasmine matchers to test our mock API data, like this matcher, toEqual().

Testing

12 of 16 Now that we have created the mock data, created our function to spy on, then spied on it with a Jasmine Spy, we can finally test our mock API data. We can treat the spied on function like any other function in our test suite, and create assertions to test the data. And, finally, let's review!

Success! The tests passed!

Let's review!

[Setup] Set up our mock data. [Setup] Set up our original function. [Setup] Placed our mock data in a beforeEach() block. [Setup] Created our spy (in beforeEach()). [Setup] Used and.callThrough() (in beforeEach()). [Setup] Put our "spied on" returns in variables (in beforeEach()). [Testing] Created our assertions with matchers. [Testing] Tested our API data. 13 of 16 And, here is a quick summary of the steps we just followed.

All Code Examples at

Github

github.com/daniellecloss/jasmine-spies-code

var Spy = function() {};

// the actual API call would normally happen here
Spy.api_data = {};

// this is the function we are testing
Spy.call_api = function(spy_name){
    if(spy_name === '' || this.api_data.data[spy_name] === undefined){
        return this.api_data.error;
        ...
14 of 16 I have the code I used for these examples hosted at my github account, if you want to take a closer look. In the repo, there is an object in one file (the code), and three tests and the data API mock, used with a Jasmine Spy, in the test spec. You can install Jasmine, download the repo into a local folder, and just type Jasmine at the command line to run the tests.

For More Information

Jasmine Spies Documentation

All examples were using Jasmine 2.0 syntax, which has changed slightly from previous versions of Jasmine.

15 of 16 All of these examples were created using Jasmine 2.0. Just an FYI, there were some syntax changes for Jasmine Spies in Jasmine 2.0. These syntax changes ARE listed in the Jasmine documentation.
16 of 16 Thanks for listening, and I hope I have been able to give you a quick taste of what it's like to use Jasmine Spies to test mock API data. Cheers!
Shaken, not Stirred Using Spies in Jasmine Created by Danielle Closs / @daniellecloss 1 of 16 Hello! I'm Danielle, and I am a UI Web developer at Arbor Networks. Sometimes I spy on my Javascript code, occasionally with a drink in hand, using Jasmine Spies.