Kingalione
Kingalione

Reputation: 4265

angularjs unit test promise based service (SQlite database service)

I'm very new in unit testing angularjs applications and I think I don't understand the main concept of testing promise based services on angularjs.

I will directly start with my example:

I have a SQLite db-service which has this method:

var executeQuery = function(db,query,values,logMessage) {
  return $cordovaSQLite.execute(db, query, values).then(function(res) {
    if(res.rows.length>0) return res;
    else return true; 
  }, function (err) {
    return false;
  });
};

And I want to write a test case, where I execute a query and then I want to get the return value of the executeQuery function of my service.

My test description is this:

describe("Test DatabaseCreateService‚", function () {
  var DatabaseCreateService,cordovaSQLite,ionicPlatform,rootScope,q;
  var db=null;

  beforeEach(module("starter.services"));
  beforeEach(module("ngCordova"));
  beforeEach(module("ionic"));

  beforeEach(inject(function (_DatabaseCreateService_, $cordovaSQLite,$ionicPlatform,$rootScope,$q) {
    DatabaseCreateService = _DatabaseCreateService_;
    cordovaSQLite = $cordovaSQLite;
    ionicPlatform = $ionicPlatform;
    q = $q;
    rootScope = $rootScope;

    ionicPlatform.ready(function() {
      db = window.openDatabase("cgClientDB-Test.db", '1', 'my', 1024 * 1024 * 100);
    });
  }));

  describe("Test DatabaseCreateService:createTableLocalValues", function() {

    it("should check that the createTableLocalValues was called correctly and return correct data", function() {
      var deferred = q.defer();

      deferred.resolve(true);

      spyOn(DatabaseCreateService,'createTableLocalValues').and.returnValue(deferred.promise);

      var promise = DatabaseCreateService.createTableLocalValues(db);
      expect(DatabaseCreateService.createTableLocalValues).toHaveBeenCalled();
      expect(DatabaseCreateService.createTableLocalValues).toHaveBeenCalledWith(db);
      expect(DatabaseCreateService.createTableLocalValues.calls.count()).toEqual(1);

      promise.then(function(resp) {
        expect(resp).not.toBe(undefined);
        expect(resp).toBe(true);
      },function(err) {
        expect(err).not.toBe(true);
      });

      rootScope.$apply();
    });
  });
});

This test description works but it does not return the value from the function instead of it return what gets resolved in deferred.resolve(true);

What I want to do is the call the DatabaseCreateService.createTableLocalValues function and resolve the data which gets returned from the function.

The createTableLocalValues function is this:

var createTableLocalValues = function(db) {
  var query = "CREATE TABLE IF NOT EXISTS `local_values` (" +
  "`Key` TEXT PRIMARY KEY NOT NULL," +
  "`Value` TEXT );";

  return executeQuery(db,query,[],"Create cg_local_values");
};

Well if I run this method on browser or device I get a true back if everything works fine and the table gets created. So how do I get this real true also in the test description and not a fake true like in my example above?

Thanks for any kind of help.

Example 2 (with callThrough):

describe('my fancy thing', function () {

  beforeEach(function() {
    spyOn(DatabaseCreateService,'createTableSettings').and.callThrough();
  });

  it('should be extra fancy', function (done) {
    var promise = DatabaseCreateService.createTableSettings(db);
    rootScope.$digest();

    promise.then(function(resp) {
      console.log(resp);
      expect(resp).toBeDefined();
      expect(resp).toBe(true);
      done();
    },function(err) {
      done();
    });
  });
});

Log message in karma-runner:

LOG: true
Chrome 46.0.2490 (Mac OS X 10.11.1) Test DatabaseCreateService‚ testing createTable functions: my fancy thing should be extra fancy FAILED
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Chrome 46.0.2490 (Mac OS X 10.11.1): Executed 42 of 42 (1 FAILED) (8.453 secs / 8.03 secs)

UPDATE:

It turned out that this problem has something to do with the $cordovaSQLite.executeQuery function itself. Somehow it have no timeout on the promise and thats what the error causes. I changed the execute function of ng-cordova to this. (hoping that this change does not break anything working)

  execute: function (db, query, binding) {
    var q = Q.defer();
    db.transaction(function (tx) {
      tx.executeSql(query, binding, function (tx, result) {
          q.resolve(result);
        },
        function (transaction, error) {
          q.reject(error);
        });
    });
    return q.promise.timeout( 5000, "The execute request took too long to respond." );
  }

With that change the tests passes correctly!

Upvotes: 4

Views: 940

Answers (2)

rouan
rouan

Reputation: 7289

A suggestion on the way you're asserting in your test:

In your test, you are calling then on your returned promise in order to make your assertions:

  promise.then(function(resp) {
    expect(resp).not.toBe(undefined);
    expect(resp).toBe(true);
  },function(err) {
    expect(err).not.toBe(true);
  });

Which is forcing you to add an assertion in an error function so that your test still fails if the promise doesn't resolve at all.

Try using Jasmine Promise Matchers instead. It will make your test code that easier to read and lead to clearer error messages when your tests fail. Your test would look something like this:

expect(promise).toBeResolvedWith(true);

Upvotes: 0

Yaron Schwimmer
Yaron Schwimmer

Reputation: 5357

You can spy on a function, and delegate to the actual implementation, using

spyOn(DatabaseCreateService,'createTableLocalValues').and.callThrough();

You might also need to call rootScope.$digest() after you call your function, so the promise will resolve.

Edit:

When testing async code, you should use the done pattern:

it('should be extra fancy', function (done) {
  var promise = DatabaseCreateService.createTableSettings(db);
  rootScope.$digest();

  promise.then(function(resp) {
    console.log(resp);
    expect(resp).toBeDefined();
    expect(resp).toBe(false);
    expect(resp).toBe(true);
    done();
  });
});

Upvotes: 4

Related Questions