简体   繁体   English

角单元测试未达到预期的间谍

[英]Angular Unit Test Failing Expected spy

I have below controller to get the books list and single books detail. 我有下面的控制器来获取书单和单书详细信息。 It's working as expected but the unit test is not working as expected. 它按预期工作,但单元测试未按预期工作。

books.controller.js 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);

Spec for above controller in which I want to test the getBook(id) service but somehow I am not able to pass the id of book. 我想在其中测试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);
            });
        });

"Get Single Book Detail: getBook()" this suit is not working. “获取单书详细信息:getBook()”这套方法不起作用。 Please help me, how to short out this kind of situation. 请帮助我,如何解决这种情况。

Error I am getting is below 我得到的错误如下

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)

EDIT (removed original, 2am answer) 编辑 (移除原件,凌晨2点答复)

Are you using strict mode? 您是否使用strict模式? There appear to be a few scoping issues going on: 似乎存在一些范围界定问题:

  1. On line 9 (in the "Get All Books List" spec), deferred is not declared, making it global implicitly 在第9行(在“获取所有图书列表”规范中),未声明deferred ,使其隐式地变为全局
  2. The last test ran on the "Get All Books List" spec fails the global deferred promise 在“获取所有书籍列表”规范上进行的最后一次测试未通过全球deferred承诺
  3. On line 60 (in the "Get Single Book Detail" spec), deferredSuccess is declared with var making it local to the function passed to inject() 在第60行(在“获取单书详细信息”规范中),使用var声明了deferredSuccess ,使其在传递给inject()的函数中处于局部状态
  4. On line 70 (the test in question), where (I assume) you meant to reject the "Single Book" deferredSuccess , you're actually failing the global/list deferred promise. 在第70行(有问题的测试)中,(我假设)您打算拒绝“ Single Book”的deferredSuccess ,实际上您未通过global / list的deferred承诺。 This has no effect, since as mentioned in item 2 that promise was already failed and Q ignores repeated rejections . 这没有效果,因为如第2项所述,promise已经失败,并且Q忽略了重复的拒绝

So, that should explain why the error is not what you think it should be. 因此,这应该可以解释为什么错误不是您认为的应该是的。

deferred isn't the only variable with scoping issues in your example; 在您的示例中, deferred不是唯一存在范围问题的变量; those should be addressed. 这些应该解决。 I suggest wrapping the file in an IFFE and using strict mode . 我建议将文件包装在IFFE中并使用严格模式 It'll make the code more predictable and avoid issues like this. 这将使代码更可预测,并避免出现此类问题。

Doing this will only get you halfway there; 这样做只会使您半途而废。 @estus's response should round out the job. @estus的回应应该使工作更加圆满。

you need to mock $rootScope. 您需要模拟$rootScope. with provide. 与提供。

The value of id is not getting avaibale in controller which is undefined . id的值在undefined控制器中不可用。

So, non-id condition getting executing. 因此,非id条件开始执行。

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

Real router should never be used in unit tests, with ngRoute module preferably be excluded from tested modules. 真正的路由器绝对不能用于单元测试中,最好将ngRoute模块从测试模块中排除。

$scope.id = routeParamId is assigned before controller instantiation, but it isn't used at all. $scope.id = routeParamId在控制器实例化之前分配,但根本没有使用。 Instead, it should be done with mocked $routeParams . 相反,应该使用$routeParams完成$routeParams

There's no $service service. 没有$service服务。 It's called BooksService . 它称为BooksService Thus getBooks isn't a spy. 因此, getBooks不是间谍。 It's preferable to mock the service completely, not only a single method. 最好完全模拟服务,而不仅仅是单个方法。

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

The test reveals the problem in controller code. 该测试在控制器代码中揭示了问题。 Since tested code is called on controller construction, $controller should be called every time in it . 由于测试的代码被称为上控制建设, $controller应该被称为在每次it A good way to avoid this is to put initialization code into $onInit method that could be tested separately. 避免这种情况的一种好方法是将初始化代码放入$onInit方法中,该方法可以单独进行测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM