未定义不是对象(评估"value.phrase.replace")



我的单元测试一直失败,并显示以下错误消息:

LOG: 'f40e0e47-6457-463b-a5f9-9dc97bd2d0ce'
LOG: [Object{phrase_id: 'f40e0e47-6457-463b-a5f9-9dc97bd2d0ce', phrase: 'Training {{group}} to develop their {{attribute}} by ensuring they are comfortable with {{factor}}'}]
PhantomJS 2.1.1 (Windows 8 0.0.0) PhraseDetailCtrl #initialisation should initialise the controller's scope with details on a single phrase FAILED
        TypeError: undefined is not an object (evaluating 'value.phrase.replace') in D:/myapp-mobile/www/js/myapp.controllers.js (line 87)
        D:/myapp-mobile/www/js/myapp.controllers.js:87:34
        forEach@[native code]
        D:/myapp-mobile/www/js/myapp.controllers.js:85:35
        then@unit-tests/phrase-detail.controller.tests.js:32:26
        PhraseDetailCtrl@D:/myapp-mobile/www/js/myapp.controllers.js:78:55
        PhraseDetailCtrl@[native code]
        instantiate@D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:18010:61
        $controller@D:/myapp-mobile/www/lib/ionic/js/ionic.bundle.js:23412:39
        D:/myapp-mobile/www/lib/angular-mocks/angular-mocks.js:2221:21
        unit-tests/phrase-detail.controller.tests.js:44:31
        loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 3 of 3 (1 FAILED) (0.01 secs / 0.084 secs)

这是我要测试的控制器:

function PhraseDetailCtrl($scope, $stateParams, PhraseService, CompetencyService, PopupService, ModalService, FilterService, $ionicLoading, $ionicActionSheet, $timeout) {
    // $ionicLoading.show({
    //  content: 'Loading',
    //  animation: 'fade-in',
    //  showBackdrop: true,
    //  maxWidth: 200,
    //  showDelay: 0
    // });

    // $timeout(function () {
        // $ionicLoading.hide();
        $scope.dataLoaded = true;
        $scope.phraseId = $stateParams.phraseId;
        console.log($scope.phraseId);
        PhraseService.getPhraseDetail($scope.phraseId).then(function(dataResponse) {
            $scope.phraseDetail = dataResponse.data;
            console.log($scope.phraseDetail);
            var replacers = FilterService.getItemFilter();
            Object.keys(replacers).forEach(function(key){
                 var value = $scope.phraseDetail;
                 value.phrase = value.phrase.replace(new RegExp(key, 'g'), replacers[key]);
            })
        })

        PhraseService.getPhraseCompetency($scope.phraseId).then(function(dataResponse) {
            $scope.phraseCompetencyList = dataResponse.data;
        })

        PhraseService.getPhraseExample($scope.phraseId).then(function(dataResponse) {
            var data = dataResponse.data;
        var phrase = data[0].phrase;
        // console.log("phrase: " + phrase);
        var tokens = data.map(function(tmp) {
            return tmp.example;
        });
        // console.log("tokens: " + tokens);
        var re = /{{[^}]*}}/gi;
        var placeholders = phrase.match(re);
        // console.log("placeholders: " + placeholders);
            if (tokens.length != placeholders.length) {
                throw new Error("Mismatch number of tokens and placeholders");
            }
            for (var i = 0; i < tokens.length; ++i) {
                var token = tokens[i];
                var placeholder = placeholders[i];
                phrase = phrase.replace(placeholder, "<button class="button button-small button-outline button-dark button-side-padding">" + token + "</button>");
            }
        // console.log(phrase);
        $scope.phraseExample = phrase;
        })

        CompetencyService.getCompetencyList().then(function(dataResponse) {
            $scope.competencyList = dataResponse.data;
        })

    // }, 1000);

    // Action sheet
    $scope.showOptions = function() {
        $ionicActionSheet.show({
            titleText: 'Options',
            buttons: [
                { text: '<i class="icon ion-heart"></i>Add to Favourites' },
                { text: '<i class="icon ion-chatbubble"></i> Suggest Improvement' }
            ],
            cancelText: 'Cancel',
            buttonClicked: function(index) {
                if(index === 0) {
                    $scope.addtoFavouritesPopup();
                } else if(index === 1) {
                    $scope.suggestimprovementModal();
                }
                //console.log('BUTTON CLICKED', index);
                return true;
            },
            cancel: function() {
                //console.log('CANCELLED');
            },
        });
    };

    // Popups
    $scope.phraseExamplePopup = function() {
        var myPopup = PopupService.phraseExamplePopup($scope);
    };
    $scope.addtoFavouritesPopup = function() {
        var myPopup = PopupService.addtoFavouritesPopup($scope);
    };

    // Modals
    $scope.phraseCompetencyModal = function() {
        var vm = $scope;
        ModalService.show('templates/modals/phrase-competency-modal.html', 'PhraseDetailCtrl as vm');
    }
    $scope.suggestimprovementModal = function() {
        var vm = $scope;
        ModalService.show('templates/modals/suggest-improvement-modal.html', 'PhraseDetailCtrl as vm');
    }
}

我认为错误信息正在发生,但我不确定如何在我的单元测试中模拟它:

Object.keys(replacers).forEach(function(key){
                 var value = $scope.phraseDetail;
                 value.phrase = value.phrase.replace(new RegExp(key, 'g'), replacers[key]);
            })

这是我的单元测试:

describe('PhraseDetailCtrl', function () {
  var $controller, $stateParams, $ionicModal, $ionicPopup, $PhraseService, $CompetencyService, $PopupService, $ModalService, $FilterService
  $stateParams = {
    phraseId: "f40e0e47-6457-463b-a5f9-9dc97bd2d0ce"
  };
  var response = {
    status: 200,
    data: [{
      "phrase_id": "f40e0e47-6457-463b-a5f9-9dc97bd2d0ce",
      "phrase": "Training {{group}} to develop their {{attribute}} by ensuring they are comfortable with {{factor}}"
    }]
  };
  beforeEach(module('ionic'));
  beforeEach(module('dingocv.services'));
  beforeEach(module('dingocv.controllers'));
  beforeEach(inject(function (_$controller_, _PhraseService_, _CompetencyService_, _ModalService_, _PopupService_, _FilterService_) {
    $controller = _$controller_;
    $PhraseService = _PhraseService_;
    $CompetencyService = _CompetencyService_;
    $ModalService = _ModalService_;
    $PopupService = _PopupService_;
    $FilterService = _FilterService_;
    spyOn(_PhraseService_, 'getPhraseDetail').and.callFake(function(){
      return{
        then: function(successCallback){
          successCallback(response);
        }
      }
    });
  }));
  describe('#initialisation', function() {
    var $scope, controller;
    beforeEach(function () {
      $scope = {};
      controller = $controller('PhraseDetailCtrl', {
        $scope: $scope,
        $stateParams: $stateParams,
        $ionicModal: $ionicModal,
        $ionicPopup: $ionicPopup,
        PhraseService: $PhraseService,
        CompetencyService: $CompetencyService,
        PopupService: $PopupService,
        ModalService: $ModalService,
        FilterService: $FilterService
      });
    });
    it('should initialise the controller's scope with details on a single phrase', function(){
      expect($PhraseService.getPhraseDetail).toHaveBeenCalled();
      expect($scope.phraseDetail).toBeDefined();
    });
  });
});

您应该在尝试使用对象之前检查null/undefined,例如

if(replacers!=undefined){
  Object.keys(replacers).forEach(function(key){
                 var value = $scope.phraseDetail;
                if(value !=undefined && value.phrase !=undefined){
                 value.phrase = value.phrase.replace(new RegExp(key, 'g'), replacers[key]);
                }
            })
}

在测试中,您有一个响应对象,其data属性为array of objects

为了访问phrase,您必须像response.data[0].phrase这样做。这里您正在访问data属性的第0 object

var response = {
    status: 200,
    data: [{
      "phrase_id": "f40e0e47-6457-463b-a5f9-9dc97bd2d0ce",
      "phrase": "Training {{group}} to develop their {{attribute}} by ensuring they are comfortable with {{factor}}"
    }]
  };

controller代码中有这个

 PhraseService.getPhraseDetail($scope.phraseId).then(function(dataResponse) { 
            // here "dataResponse.data" is returning an array
            $scope.phraseDetail = dataResponse.data;
            console.log($scope.phraseDetail);
            var replacers = FilterService.getItemFilter();
            var i = 0;
            Object.keys(replacers).forEach(function(key){
                 // Since $scope.phraseDetail is an array of objects, you have to select particular object in the array. 
                 var value = $scope.phraseDetail[i];
                 value.phrase = value.phrase.replace(new RegExp(key, 'g'), replacers[key]);
                 i++;
            })
        })

最新更新