2017-07-16 41 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方法中。