2017-07-16 18 views
0

私は書籍リストと単行本の詳細を得るために以下のコントローラを持っています。期待どおりに動作していますが、単体テストが期待どおりに動作していません。私はgetBook(ID)サービスをテストしたいが、どういうわけか、私は本のIDを渡すことができないですした上でのコントローラのための角度単位のテストが失敗するスパイ

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); 

仕様。

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

EDIT(削除元、2AMの答え)

あなたはstrictモードを使用していますか?起こっていくつかのスコープの問題があるように見える:9行目

  1. (「すべての書籍一覧の取得」仕様で)、deferredが宣言されていない、最後のテストは、上で実行
  2. 暗黙的にそれがグローバル作りますdeferredSuccessvarがライン70でinject()
  3. に渡された関数には、ローカル作ると宣言され、スペック(「シングル書籍の詳細を取得」仕様で)グローバルdeferred約束ライン60で
  4. を失敗した「すべての書籍リストを取得します」 (問題のテスト)、あなたが「単一の本」を拒否することを意図していた(私が仮定した)deferredSuccess、あなたは実際にはグローバル/リストdeferred約束に失敗しています。これは、項目2で言及されたように、約束が既に失敗しており、Q ignores repeated rejectionsのため、何の効果もありません。

これは、エラーが原因と思われる理由ではないことを説明する必要があります。

deferredは、例でスコープの問題がある唯一の変数ではありません。それらは対処すべきである。ファイルをIFFEにラップし、strict modeを使用することをおすすめします。それはコードをより予測可能にし、このような問題を避けるでしょう。

これを実行すると、途中までしか届かなくなります。 @ estusの応答は、仕事を丸めてください。

+0

詳細を追加するだけです。期待しているレスポンスが表示されない理由は、テストしているメソッドをオーバーライドして明示的に 'promise.resolve'を返すように指示したためです。空で解決された約束を返すことはできません。 –

+0

サービスではなく、テスト中のコントローラです。従ってサービスは嘲笑されなければならない。 – estus

+0

より正確な答えで更新されました。それは私が午前2時に質問に答えるために得られるものです... –

0

$rootScope.を提供する必要があります。

undefinedのコントローラでは、idの値が有効ではありません。

したがって、非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 

このテストでは、コントローラコードの問題が明らかです。テストされたコードはコントローラ構築時に呼び出されるため、$controllerは毎回itで呼び出される必要があります。これを回避する良い方法は、個別にテストできる$onInitメソッドに初期化コードを入れることです。

関連する問題