【问题标题】:angularjs unit test promise based service (SQlite database service)基于 angularjs 单元测试承诺的服务(SQlite 数据库服务)
【发布时间】:2016-02-18 21:06:46
【问题描述】:

我是单元测试 angularjs 应用程序的新手,我想我不明白在 angularjs 上测试基于 Promise 的服务的主要概念。

我将直接从我的例子开始:

我有一个 SQLite 数据库服务,它有这个方法:

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;
  });
};

我想编写一个测试用例,在其中执行一个查询,然后我想获取我的服务的 executeQuery 函数的返回值。

我的测试说明是这样的:

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();
    });
  });
});

此测试描述有效,但它不从函数返回值,而是返回在deferred.resolve(true); 中解析的内容

我要做的是调用 DatabaseCreateService.createTableLocalValues 函数并解析从函数返回的数据。

createTableLocalValues 函数是这样的:

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");
};

好吧,如果我在浏览器或设备上运行此方法,如果一切正常并且表被创建,我会得到真正的回报。那么我如何在测试描述中也得到这个真实的真实而不是像我上面的例子那样的假真实呢?

感谢您的任何帮助。

示例 2(带有 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();
    });
  });
});

在 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):执行 42 次,共 42 次(1 次失败)(8.453 秒 / 8.03 秒)

更新:

原来这个问题与 $cordovaSQLite.executeQuery 函数本身有关。不知何故,它没有超时,这就是错误的原因。我把 ng-cordova 的执行功能改成了这个。 (希望此更改不会破坏任何工作)

  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." );
  }

通过该更改,测试可以正确通过!

【问题讨论】:

    标签: angularjs unit-testing jasmine karma-jasmine angular-promise


    【解决方案1】:

    关于您在测试中断言的方式的建议:

    在您的测试中,您在返回的 Promise 上调用 then 以做出断言:

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

    这会迫使您在错误函数中添加断言,这样如果承诺根本无法解决,您的测试仍然会失败。

    尝试改用Jasmine Promise Matchers。它将使您的测试代码更易于阅读,并在您的测试失败时导致更清晰的错误消息。您的测试将如下所示:

    expect(promise).toBeResolvedWith(true);
    

    【讨论】:

    • 我尝试使用 jasmine 承诺匹配器,但承诺似乎没有得到正确解决。我得到:如果我使用 expect(promise).toBeResolved();能否请您更改测试用例,以便我可以了解如何使用 jasmine 承诺匹配器,因为它的文档不是很好
    • @Kingalione 如果您遇到该错误,可能是因为您在expect() 中包装的变量未定义。它需要是一个承诺。如果您想调试当前值,可以在断言之前执行console.log(promise)
    【解决方案2】:

    你可以监视一个函数,并委托给实际的实现,使用

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

    您可能还需要在调用函数后调用rootScope.$digest(),这样promise 才会解决。

    编辑:

    在测试异步代码时,您应该使用done 模式:

    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();
      });
    });
    

    【讨论】:

    • 我按照你说的尝试过,但它不能正常工作。现在它通过了测试,但值不正确。我希望得到一个真实的并且正在检查虚假并且它通过了。所以肯定有另一个失败。我已经编辑了我的帖子,请查看示例 2。
    • 你应该调试你的代码。可能在代码中返回 false 之前打印 err
    • 我正在使用日志调试代码,我删除了这篇文章中的日志。好吧,我不认为函数运行失败是因为我可以这样做:expect(resp).toBe(false);期望(resp).toBe(真);在同一个测试用例中它仍然通过
    • 哦。我错过了那部分......当您进行异步函数测试时,您应该将done 添加到您的测试中并稍后调用它。请参阅文档jasmine.github.io/2.3/…
    • 是的,我也尝试过,但遇到了超时错误。我已经更新了我的帖子。我不太确定我是否在第二个示例中使用了 done 函数。
    猜你喜欢
    • 2014-05-19
    • 2016-02-17
    • 2014-05-14
    • 2013-06-29
    • 2014-06-17
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多