2017-07-16 45 views
0

我有下面的控制器来获取书籍清单和单一书籍的细节。它按预期工作,但单元测试未按预期工作。角单元测试失败预期的间谍

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)服务,但不知何故,我不能传书的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) 

回答

0

编辑

是否使用strict模式(去除原来,凌晨2点的答案)?似乎有一些范围的问题回事:

  1. 在第9行(在“获取的所有书籍清单”规范),deferred不宣,使其成为全球隐含
  2. 的最后一次测试在跑“把所有的书籍清单”规范失败全球deferred承诺
  3. 线60(在“找一本书详细介绍”规范),deferredSuccessvar使其本地传递给inject()
  4. 在线70函数声明(有问题的测试),在哪里(我假设)你打算拒绝“单本书”deferredSuccess,你实际上在全球/列表deferred承诺失败。这不起作用,因为如第2项所述,承诺已经失败,并且Q ignores repeated rejections

所以,这应该解释为什么错误不是你认为应该是。

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

做到这一点只会让你一半在那里; @ estus的回应应该完成这项工作。

+0

只是为了添加更多的细节。你没有看到你期待的回应的原因是你重写了你正在测试的方法,并明确地告诉它总是返回'promise.resolve'。除了空洞的承诺之外,它不能返回任何东西。 –

+0

这是一个正在测试的控制器,而不是服务。因此,服务必须被嘲笑。 – estus

+0

用更正确的答案更新。这就是我在凌晨2点回答问题的原因。 –

0

你需要模拟$rootScope.提供。

id的值在控制器undefined中没有得到avaibale。

所以,非id条件执行。

$scope = _$rootScope_.$new(); 
    $scope.id = routeParamId; 
    module(function ($provide) { 
    $provide.value('$rootScope', scope); //mock rootscope with id 
    }); 
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 

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