Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 536
Kingalione Avatar asked Oct 19 '25 09:10

Kingalione


1 Answers

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();
  });
});
like image 170
Yaron Schwimmer Avatar answered Oct 21 '25 22:10

Yaron Schwimmer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!