web-dev-qa-db-ja.com

Jasmineがpromise.then関数をテストする

Jasmineでアプリをテストしようとすると、次の問題が発生しました。
約束のthen関数で何かを計算します。それが、コードをテストする必要があるポイントです。

ここに私のコントローラーのコードがあります:

  TestCtrl.$inject = ["$scope", "TestService"];
  /* ngInject */
  function TestCtrl($scope, TestService) {
    $scope.loadData = function () {
      TestService.getData().then(function (response) {
        $scope.data = response.data;
        $scope.filtered = $scope.data.filter(function(item){
          if(item.id > 1000){
            return true;
          }
          return false;
        })
      });
    }
  }

そして、私のジャスミンのテストコード:

describe('TestService tests', function () {
  var $q;
  beforeEach(function () {
    module('pilot.fw.user');
  });
  beforeEach(inject(function (_$q_) {
    $q = _$q_;
  }));
  describe('UserController Tests', function () {

    beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
      this.scope = $rootScope.$new();
      this.$rootscope = $rootScope;
      this.$httpBackend = _$httpBackend_;
      this.scope = $rootScope.$new();
      var TestServiceMock = {
        getData: function () {
          var deferred = $q.defer();
          var result = [{
            "id": 1720,
            "user": 1132
          },
            {
              "id": 720,
              "user": 132
            }, {
              "id": 1721,
              "user": 1132
            }];
          deferred.promise.data = result;
          deferred.resolve(result);
          return deferred.promise;
        }
      };
      this.controller = $controller('TestCtrl', {
        '$scope': this.scope,
        'TestService': TestServiceMock
      });
    }));

    it('test', function(){
      this.scope.loadData();
      expect(true).toBeTruthy();
    })
  });
});

私が理解できない奇妙なことは次のとおりです(コンソールログでテスト済み):

  • 私の約束が作成されて返されます
  • 私のloadData関数が呼び出され、TestServiceからgetData()関数が呼び出されます
  • 解決された約束を返しますが、then関数内のすべては実行されません

それでは、どうすればthen関数内のコードをテストできますか?
手伝ってくれてありがとう

32
Jonas Hans

ジャスミン 'it'メソッドは、非同期テストのために呼び出すことができるdoneパラメーターを取ります

it('Should be async', function(done) {
  someAsyncFunction().then(function(result) {
    expect(result).toBe(true);
    done();
  });
});

好きなだけ深く行ってください。すべてが終了したら必ずdoneを呼び出してください。 Jasmineのデフォルトのタイムアウトはテストごとに5秒です。そのため、非同期の処理が完了しない場合、jasmineはクラッシュします。 configsでこの設定を変更するか、ターミナルで設定できます。

これは、ジャスミンのドキュメントから直接、デフォルトのタイムアウト間隔を処理する方法を示しています

describe("long asynchronous specs", function() {
  var originalTimeout;
  beforeEach(function() {
    originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
  });

  it("takes a long time", function(done) {
    setTimeout(function() {
      done();
    }, 9000);
  });

  afterEach(function() {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
  });
});

10秒以内に機能しない場合、メソッドに誤りがある可能性があると思います。特に、ローカルサーバー/ dbと通信している場合。 HEAVY計算を実行している場合、またはそれほどインターネットに接続していない外部APIにアクセスしている場合にのみ、この処理にかかる時間は長くなります。すべてがローカル(またはスタブ/モック!)の場合、5〜10秒を超えるものは明確な赤旗です。

39
Dustin Stiles

このソリューションが役立つことを願っています。テスト時に有用だとわかったアプローチの1つは、依存関係のモックです。私はできる限りのことをコメントアウトしようとしました。

var returnMock, $scope, TestServiceMock, controller;

beforeEach(module('app'));

beforeEach(inject(function($controller) {
    returnMock = {
        then: jasmine.createSpy(),
    };
    $scope = {};
    // first assumption is You are testing TestService extensively,
    // I don't care about what getData has to do to get results
    // All I care about is it gets called when I call loadData
    TestServiceMock = {
        getData: jasmine.createSpy().and.returnValue(returnMock);
    };

    controller = $controller;
}));

it('should load data when loadData function is called and result set is 
under 1000', function() {
    controller('TestCtrl', {
        $scope,
        TestServiceMock
    });
    // another assumption is your data comes back in such a format
    // perhaps in the actual code check whether data exists and proceed
    // or do some other action
    var returnedData = {
        data: [
            {
                id: 1,
                name: 'item 1',
            },
        ]
    }
    // when I execute the function/method
    $scope.loadData();
    // I expect getData to be called
    expect(TestServiceMock.getData).toHaveBeenCalled();
    // I expect then to be called and the reason is I mocked it
    expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
    returnMock.then.calls.mostRecent().args[0](returnedData);
    // expect data on scope to be equal to my mocked data
    expect($scope.data).toEqual(returnedData.data);
    // don't expect any result because 1 < 1000
    expect($scope.filtered).toEqual([]);
    expect($scope.filtered.length).toEqual(0);
});

it('should load data when loadData function is called and result set is over 1000', 
     function() {
    controller('TestCtrl', {
        $scope,
        TestServiceMock
    });
    var returnedData = {
        data: [
            {
                id: 1,
                name: 'item 1',
            },
            {
                id: 1000,
                name: 'item 1000',
            },
            {
                id: 1001,
                name: 'item 1000',
            },
            {
                id: 1002,
                name: 'item 1002',
            }
        ]
    }
    $scope.loadData();
    expect(TestServiceMock.getData).toHaveBeenCalled();
    expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
    returnMock.then.calls.mostRecent().args[0](returnedData);
    expect($scope.data).toEqual(returnedData.data);
    // expect a result because some entries in the mocked data have id > 1000
    expect($scope.filtered).toEqual([
        {
            id: 1001,
            name: 'item 1000',
        },
        {
            id: 1002,
            name: 'item 1002',
        }]);
    expect($scope.filtered.length).toEqual(2);
});

公式Jasmine Docs ほとんどの概念を広範囲にわたって説明します。ソリューションが役立つことを願っています!!!!

5
mahad

Angular 1.xおよび2.x +プロジェクトについて、私がやっていることを教えてください。 angularテストツールを使用して、非同期テストのコールバック/ネストを取り除きます。 angular 1.xでは、$ qと$ rootScope。$ apply()の組み合わせを使用することを意味します。 angular 2.x +では、fakeAsyncのようなものを使用することを意味します。

Angular 1.xドキュメントから

it('should simulate promise', inject(function($q, $rootScope) {
  var deferred = $q.defer();
  var promise = deferred.promise;
  var resolvedValue;

  promise.then(function(value) { resolvedValue = value; });
  expect(resolvedValue).toBeUndefined();

  // Simulate resolving of promise
  deferred.resolve(123);
  // Note that the 'then' function does not get called synchronously.
  // This is because we want the promise API to always be async, whether or not
  // it got called synchronously or asynchronously.
  expect(resolvedValue).toBeUndefined();

  // Propagate promise resolution to 'then' functions using $apply().
  $rootScope.$apply();
  expect(resolvedValue).toEqual(123);
}));

欠点は、コードがアンギュラーに関連付けられていることです。利点は、コードがフラットであり、2.x +に移植できることです。

私はテストで約束を返すことができるモカテストランナーのファンでしたが、あなたはそれを実現しようとすることができましたが、テストのためにコードを特別に変更する必要があるなどの欠点もあります。

2
Jacob McKay

コントローラに関しては、そのような値を「返す」必要があります。

TestCtrl.$inject = ["$scope", "TestService"];
/* ngInject */
function TestCtrl($scope, TestService) {
  $scope.loadData = function () {
    // Return this call, since it will return a new promise
    // This is what let's you do $scope.loadData.then()
    return TestService.getData().then(function (response) {
      // What you return in here will be the first argument
      // of your then method, in the tests / any env
      // Ex. return 'foo'
      // will result in .then(result => result === 'foo') //=> true
      // return one of these, i suggest the data, go SRP!
      return $scope.data = response.data;

      // I would do this stuff in a separate function, but you
      // can return 'filtered' instead if you like.
      //
      // $scope.filtered = $scope.data.filter(function(item){
      //   if(item.id > 1000){
      //     return true;
      //   }
      //   return false;
      // });
    });
  }
}

「then」の後に何かを呼び出すことは何の意味も持たないことを忘れないでください。値は「then」内で呼び出す必要があります。それの後でも、前でもない。しかし、その中。トム・グリーンやフレディ・ゴット・フィンガードの貧弱なムースのように。

0
Dustin Stiles

これをよく見る https://codecraft.tv/courses/angular/unit-testing/asynchronous/

実際には3つの方法があります。

1)定期的に使用する:

it('test', (done) => {
   const spy = spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
   spy.calls.mostRecent().returnValue.then(res => {
      ...your expect here...
      done();
   })
} );

2)beforeEachおよびそれでasyncを使用します。

it('test', async(() => {
    spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
    fixture.whenStable().then(res => {
       ...your expect here...
    })
 } ));

3)HttpまたはXHR呼び出しがない場合は、fakeAsyncを使用します。

it('test', fakeAsync(() => {
    spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
    tick();
    ...your expect here...
 } ));
0
Yevheniy Potupa