How to write a unit test with Jest for code with Promise

10,957

Solution 1

This frustrated me most of the day today. Here is what I ended up with (testing my ActionCreator (Flux) which uses an API that returns promises and dispatches stuff based on the promise). Basically I mock out the API method that returns the promise and resolve it right away. You'd think that this would be enough to get the .then(...) methods to fire, but the pit code was required to have my ActionCreator actually do work based on the resolved promise.

jest.dontMock('../LoginActionCreators.js');
jest.dontMock('rsvp'); //has to be above the require statement

var RSVP = require('rsvp'); //could be other promise library

describe('LoginActionCreator', function() {
  pit('login: should call the login API', function() {
    var loginActionCreator = require('../LoginActionCreators');
    var Dispatcher = require('../../dispatcher/Dispatcher');
    var userAPI = require('../../api/User');
    var Constants = require('../../constants/Constants');

    //my api method needs to return this
    var successResponse = { body: {"auth_token":"Ve25Mk3JzZwep6AF7EBw=="} };

    //mock out the API method and resolve the promise right away
    var apiMock = jest.genMockFunction().mockImplementation(function() {
      var promise = new RSVP.Promise(function(resolve, reject) {
        resolve(successResponse);
      });

      return promise;
    });
    //my action creator will dispatch stuff based on the promise resolution, so let's mock that out too
    var dispatcherMock = jest.genMockFunction();

    userAPI.login = apiMock;
    Dispatcher.dispatch = dispatcherMock;

    var creds = {
      username: 'username',
      password: 'password'
    };

    //call the ActionCreator
    loginActionCreator.login(creds.username, creds.password);

    //the pit code seems to manage promises at a slightly higher level than I could get to on my 
    // own, the whole pit() and the below return statement seem like they shouldnt be necessary 
    // since the promise is already resolved in the mock when it is returned, but 
    // I could not get this to work without pit.
    return (new RSVP.Promise(function(resolve) { resolve(); })).then(function() {
      expect(apiMock).toBeCalledWith(creds);
      expect(dispatcherMock.mock.calls.length).toBe(2);
      expect(dispatcherMock.mock.calls[0][0]).toEqual({ actionType: Constants.api.user.LOGIN, queryParams: creds, response: Constants.request.PENDING});
      expect(dispatcherMock.mock.calls[1][0]).toEqual({ actionType: Constants.api.user.LOGIN, queryParams: creds, response: successResponse});
    });
  });
});

Here is the ActionCreator which ties the API to the Dispatcher:

'use strict';

var Dispatcher = require('../dispatcher/Dispatcher');
var Constants = require('../constants/Constants');
var UserAPI = require('../api/User');


function dispatch(key, response, params) {
  var payload = {actionType: key, response: response};
  if (params) {
    payload.queryParams = params;
  }
  Dispatcher.dispatch(payload);
}

var LoginActionCreators = {

  login: function(username, password) {
    var params = {
        username: username,
        password: password
    };

    dispatch(Constants.api.user.LOGIN, Constants.request.PENDING, params);

    var promise = UserAPI.login(params);

    promise.then(function(res) {
      dispatch(Constants.api.user.LOGIN, res, params);
    }, function(err) {
      dispatch(Constants.api.user.LOGIN, Constants.request.ERROR, params);
    });
  }
};

module.exports = LoginActionCreators; 

Solution 2

This tutorial on the website of Jest doesn't answer the question directly, but has the gist of how to unit test promise.

https://facebook.github.io/jest/docs/tutorial-async.html

Share:
10,957

Related videos on Youtube

mu_sa
Author by

mu_sa

Updated on June 15, 2022

Comments

  • mu_sa
    mu_sa almost 2 years

    I am trying to write a unit test with Jest and Jasmine-pit for the below code and am totally stumped with it. The code is an ajax call which retrieves some data from resource and saves it in the variable.

    init = function() {
        var deferred = Q.defer();
        $.ajax({
            type: 'GET',
            datatype: 'json',
            url: window.location.origin + name,
            success: function (data) {
                userId = data.userId;
                apiKey = data.apiKey;
                deferred.resolve();
            }
        });
        return deferred.promise;
    },