如何使用 Jasmine 测试从 AngularJS 控制器返回的 Promise 值?

2022-01-11 00:00:00 angularjs jasmine javascript promise q

我有一个控制器,它公开了一个函数,该函数在休息调用后返回一些文本.它工作正常,但我无法用 Jasmine 测试它.测试中 Promise 处理程序中的代码永远不会执行.

I have a controller that expose a function that returns some text after a rest call. It works fine, but I'm having trouble testing it with Jasmine. The code inside the promise handler in the test never executes.

控制器:

/* global Q */
'use strict';
angular.module('myModule', ['some.service'])
    .controller('MyCtrl', ['$scope', 'SomeSvc', function ($scope, SomeSvc) {

        $scope.getTheData = function (id) {
            var deferred = Q.defer();
            var processedResult = '';
            SomeSvc.getData(id)
                .then(function (result) {
                    //process data
                    processedResult = 'some stuff';
                    deferred.resolve(processedResult);
                })
                .fail(function (err) {
                    deferred.reject(err);
                });
            return deferred.promise;
        }
    }]);

测试:

describe('some tests', function() {
    var $scope;
    var $controller;
    var $httpBackend;
    beforeEach(function() {
        module('myModule');
        inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
            $scope = _$rootScope_.$new();
            $controller = _$controller_;
            $httpBackend = _$httpBackend_;
            //mock the call that the SomeSvc call from the controller will make
            $httpBackend.expect('GET', 'the/url/to/my/data');
            $httpBackend.whenGET('the/url/to/my/data')
                .respond({data:'lots of data'});
            $controller ('MyCtrl', {
                $scope: $scope
            });
        });
    });

    describe('test the returned value from the promise', function() {
        var prom = $scope.getTheData(someId);
        prom.then(function(result) {
            expect(result).toBe('something expected');  //this code never runs
        })
    });
});

推荐答案

除非调用 promise 回调,否则 then 中的任何内容都不会运行 - 这可能会像您遇到的那样出现误报风险这里.测试将通过这里,因为期望从未运行过.

Anything inside a then will not be run unless the promise callbacks are called - which is a risk for a false positive like you experienced here. The test will pass here since the expect was never run.

有很多方法可以确保您不会得到这样的误报.例子:

There are many ways to make sure you don't get a false positive like this. Examples:

Jasmine 将等待承诺在超时内得到解决.

Jasmine will wait for the promise to be resolved within the timeout.

  • 如果不及时解决,测试将失败.
  • 如果 promise 被拒绝,测试也会失败.

当心如果您忘记返回,您的测试将给出误报!

Beware If you forget the return, your test will give a false positive!

describe('test the returned value from the promise', function() {
    return $scope.getTheData(someId)
    .then(function(result) {
        expect(result).toBe('something expected');
    });
});

B) 使用 Jasmine 提供的 done 回调给测试方法

  • 如果在超时时间内没有调用 done,则测试将失败.
  • 如果使用参数调用 done,则测试将失败.
    这里的catch会将错误传递给jasmine,你会看到错误在输出中.
  • B) Use the done callback provided by Jasmine to the test method

    • If done is not called within the timeout the test will fail.
    • If done is called with arguments the test will fail.
      The catch here will pass the error to jasmine and you will see the error in the output.
    • 当心如果您忘记了捕获,您的错误将被吞没,您的测试将因一般超时错误而失败.

      Beware If you forget the catch, your error will be swallowed and your test will fail with a generic timeout error.

      describe('test the returned value from the promise', function(done) {
          $scope.getTheData(someId)
          .then(function(result) {
              expect(result).toBe('something expected');
              done();
          })
          .catch(done);
      });
      

      C) 使用间谍和手摇(同步测试)

      如果你不完美,这可能是编写测试最安全的方式.

      C) Using spies and hand cranking (synchronous testing)

      If you're not perfect this might be the safest way to write tests.

      it('test the returned value from the promise', function() {
          var
            data = { data: 'lots of data' },
            successSpy = jasmine.createSpy('success'),
            failureSpy = jasmine.createSpy('failure');
      
          $scope.getTheData(someId).then(successSpy, failureSpy);
      
          $httpBackend.expect('GET', 'the/url/to/my/data').respond(200, data);
          $httpBackend.flush();
      
          expect(successSpy).toHaveBeenCalledWith(data);
          expect(failureSpy).not.toHaveBeenCalled();
      });
      

      同步测试技巧
      您可以在需要时手动调整 httpBackend、超时和范围更改,以使控制器/服务更进一步.$httpBackend.flush(), $timeout.flush(), scope.$apply().

相关文章