角度单位测试失败预期的间谍

时间:2017-07-16 05:18:27

标签: javascript angularjs unit-testing jasmine karma-jasmine

我有以下控制器来获取书籍清单和单书详细信息。它按预期工作,但单元测试没有按预期工作。

books.controller.js

var myApp = angular.module('myApp');

function BooksController($log, $routeParams, BooksService) {

    // we declare as usual, just using the `this` Object instead of `$scope`
    const vm = this;
    const routeParamId = $routeParams.id;

    if (routeParamId) {
        BooksService.getBook(routeParamId)
            .then(function (data) {
                $log.info('==> successfully fetched data for book id:', routeParamId);
                vm.book = data;
            })
            .catch(function (err) {
                vm.errorMessage = 'OOPS! Book detail not found';
                $log.error('GET BOOK: SOMETHING GOES WRONG', err)
            });
    }

    BooksService.getBooks()
        .then(function (data) {
            $log.info('==> successfully fetched data');
            vm.books = data;
        })
        .catch(function (err) {
            vm.errorMessage = 'OOPS! No books found!';
            $log.error('GET BOOK: SOMETHING GOES WRONG', err)
        });

}
BooksController.$inject = ['$log', '$routeParams', 'BooksService'];
myApp.controller('BooksController', BooksController);

我想测试getBook(id)服务的上述控制器的规范,但不知怎的,我无法传递book的id。

describe('Get All Books List: getBooks() =>', () => {
        const errMsg = 'OOPS! No books found!';
        beforeEach(() => {
            // injecting rootscope and controller
            inject(function (_$rootScope_, _$controller_, _$q_, BooksService) {
                $scope = _$rootScope_.$new();
                $service = BooksService;
                $q = _$q_;
                deferred = _$q_.defer();

                // Use a Jasmine Spy to return the deferred promise
                spyOn($service, 'getBooks').and.returnValue(deferred.promise);

                // The injector unwraps the underscores (_) from around the parameter names when matching
                $vm = _$controller_('BooksController', {$scope: $scope, $service: BooksService});
            });

        });

        it('should defined getBooks $http methods in booksService', () => {
            expect(typeof $service.getBooks).toEqual('function');
        });

        it('should able to fetch data from getBooks service', () => {
            // Setup the data we wish to return for the .then function in the controller
            deferred.resolve([{ id: 1 }, { id: 2 }]);

            // We have to call apply for this to work
            $scope.$apply();

            // Since we called apply, now we can perform our assertions
            expect($vm.books).not.toBe(undefined);
            expect($vm.errorMessage).toBe(undefined);
        });

        it('should print error message if data not fetched', () => {

            // Setup the data we wish to return for the .then function in the controller
            deferred.reject(errMsg);

            // We have to call apply for this to work
            $scope.$apply();

            // Since we called apply, now we can perform our assertions
            expect($vm.errorMessage).toBe(errMsg);
        });
    });

describe('Get Single Book Detail: getBook() =>', () => {
            const errMsg = 'OOPS! Book detail not found';
            const routeParamId = '59663140b6e5fe676330836c';
            beforeEach(() => {

                // injecting rootscope and controller
                inject(function (_$rootScope_, _$controller_, _$q_, BooksService) {
                    $scope = _$rootScope_.$new();
                    $scope.id = routeParamId;
                    $service = BooksService;
                    $q = _$q_;
                    var deferredSuccess = $q.defer();

                    // Use a Jasmine Spy to return the deferred promise
                    spyOn($service, 'getBook').and.returnValue(deferredSuccess.promise);
                    // The injector unwraps the underscores (_) from around the parameter names when matching
                    $vm = _$controller_('BooksController', {$scope: $scope, $service: BooksService});
                });

            });

            it('should defined getBook $http methods in booksService', () => {
                expect(typeof $service.getBook).toEqual('function');

            });

            it('should print error message', () => {
                // Setup the data we wish to return for the .then function in the controller
                deferred.reject(errMsg);

                // We have to call apply for this to work
                $scope.$apply();

                // expect($service.getBook(123)).toHaveBeenCalled();
                // expect($service.getBook(123)).toHaveBeenCalledWith(routeParamId);
                // Since we called apply, now we can perform our assertions
                expect($vm.errorMessage).toBe(errMsg);
            });
        });

"获取单本书详情:getBook()"这套西装不行。请帮助我,如何缩短这种情况。

我得到的错误低于

Chrome 59.0.3071 (Mac OS X 10.12.5) Books Controller Get Single Book Detail: getBook() => should print error message FAILED
        Expected 'OOPS! No books found!' to be 'OOPS! Book detail not found'.
Chrome 59.0.3071 (Mac OS X 10.12.5) Books Controller Get Single Book Detail: getBook() => should print error message FAILED
        Expected 'OOPS! No books found!' to be 'OOPS! Book detail not found'.
            at Object.it (test/client/controllers/books.controller.spec.js:108:38)
 Chrome 59.0.3071 (Mac OS X 10.12.5): Executed 7 of 7 (1 FAILED) (0 secs / 0.068 secs)
.
Chrome 59.0.3071 (Mac OS X 10.12.5): Executed 7 of 7 (1 FAILED) (0.005 secs / 0.068 secs)

3 个答案:

答案 0 :(得分:0)

编辑(删除原文,凌晨2点答案)

您使用的是strict模式吗?似乎有一些范围问题正在发生:

  1. 在第9行(在“获取所有图书清单”规范中),未声明deferred,使其隐式全局
  2. 在“获取所有图书清单”规范上运行的最后一次测试未通过全球deferred承诺
  3. 在第60行(在“获取单一书籍详细信息”规范中),deferredSuccess声明为var,使其成为传递给inject()的函数的本地
  4. 在第70行(有问题的测试),其中(我假设)您打算拒绝“单书”deferredSuccess,实际上您实际上未通过全局/列表deferred承诺。这没有任何效果,因为如第2项所述,承诺已经失败并且Q ignores repeated rejections
  5. 所以,这应该解释为什么错误不是你认为应该的错误。

    deferred并不是您示例中唯一存在范围问题的变量;应该解决这些问题。我建议将文件包装在IFFE并使用strict mode。它将使代码更具可预测性并避免出现类似问题。

    这样做只会让你到达那里; @ estus的回应应该完成这项工作。

答案 1 :(得分:0)

你需要通过提供来模拟$rootScope.

id的值未在undefined的控制器中获取。

因此,非id条件正在执行。

   $scope = _$rootScope_.$new();
   $scope.id = routeParamId;
   module(function ($provide) {
     $provide.value('$rootScope', scope); //mock rootscope with id
   });

答案 2 :(得分:0)

不应在单元测试中使用真实路由器,最好将ngRoute模块从测试模块中排除。

$scope.id = routeParamId在控制器实例化之前分配,但它根本不被使用。相反,它应该使用模拟的$routeParams来完成。

没有$service服务。它被称为BooksService。因此getBooks不是间谍。最好是完全模拟服务,而不仅仅是一种方法。

mockedBooksService = jasmine.createSpyObj('BooksService', ['getBooks']);

var mockedData1 = {};
var mockedData2 = {};
mockedBooksService.getBooks.and.returnValues(
  $q.resolve(mockedData1),
  $q.resolve(mockedData2),
);
$vm = $controller('BooksController', {
  $scope: $scope,
  BooksService: mockedBooksService,
  $routeParams: { id: '59663140b6e5fe676330836c' }
});

expect(mockedBooksService.getBooks).toHaveBeenCalledTimes(2);
expect(mockedBooksService.getBooks.calls.allArgs()).toEqual([
  ['59663140b6e5fe676330836c'], []
]);

$rootScope.$digest();

expect($vm.book).toBe(mockedData2);

// then another test for falsy $routeParams.id

测试显示控制器代码中的问题。由于在控制器构造上调用了测试代码,因此每次$controller都应调用it。避免这种情况的一个好方法是将初始化代码放入可以单独测试的$onInit方法中。