Jasmine ile bir AngularJS hizmetini nasıl test ederim?


108

(Burada ilgili bir soru var: Jasmine testi, AngularJS modülünü görmüyor )

Angular'ı önyüklemeden bir hizmeti test etmek istiyorum.

Bazı örneklere ve öğreticiye baktım ama hiçbir yere gitmiyorum.

Sadece üç dosyam var:

  • myService.js: AngularJS hizmetini tanımladığım yer

  • test_myService.js: hizmet için bir Jasmine testi tanımladığım yer.

  • specRunner.html: normal yasemin yapılandırmasına sahip ve önceki diğer iki dosyayı ve Jasmine, Angularjs ve angular-mocks.js'yi içe aktardığım bir HTML dosyası.

Bu, hizmetin kodudur (test etmediğimde beklendiği gibi çalışır):

var myModule = angular.module('myModule', []);

myModule.factory('myService', function(){

    var serviceImplementation   = {};
    serviceImplementation.one   = 1;
    serviceImplementation.two   = 2;
    serviceImplementation.three = 3;

    return serviceImplementation

});

Hizmeti tek başına test etmeye çalıştığım için, ona erişebilmeli ve yöntemlerini kontrol edebilmeliyim. Sorum şu: AngularJS'yi önyüklemeden testime hizmeti nasıl enjekte edebilirim?

Örneğin, Jasmine ile bir hizmet yöntemi için döndürülen değeri şu şekilde nasıl test edebilirim:

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){
            myModule = angular.module('myModule');
                    //something is missing here..
            expect( myService.one ).toEqual(1);
        })

    })

});

Yanıtlar:


137

Sorun, hizmeti başlatan fabrika yönteminin yukarıdaki örnekte çağrılmamasıdır (yalnızca modülü oluşturmak hizmeti başlatmaz).

Hizmetin somutlaştırılabilmesi için angular.injector'ın hizmetimizin tanımlandığı modül ile çağrılması gerekmektedir. Ardından, hizmet için yeni enjektör nesnesine sorabiliriz ve ancak o zaman hizmet nihayet somutlaştırıldığında.

Bunun gibi bir şey çalışır:

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){
            var $injector = angular.injector([ 'myModule' ]);
            var myService = $injector.get( 'myService' );
            expect( myService.one ).toEqual(1);
        })

    })

});

Başka bir yol da hizmeti ' invoke ' kullanarak bir işleve geçirmek olabilir :

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){

            myTestFunction = function(aService){
                expect( aService.one ).toEqual(1);
            }

            //we only need the following line if the name of the 
            //parameter in myTestFunction is not 'myService' or if
            //the code is going to be minify.
            myTestFunction.$inject = [ 'myService' ];

            var myInjector = angular.injector([ 'myModule' ]);
            myInjector.invoke( myTestFunction );
        })

    })

});

Ve son olarak, bunu yapmanın 'uygun' yolu, 'Her Birinden ' yasemin bloğunda ' enjekte ' ve ' modül ' kullanmaktır . Bunu yaparken, 'enjekte' işlevinin standart angularjs paketinde değil, ngMock modülünde olduğunu ve sadece yasemin ile çalıştığını anlamalıyız.

describe('myService test', function(){
    describe('when I call myService.one', function(){
        beforeEach(module('myModule'));
        it('returns 1', inject(function(myService){ //parameter name = service name

            expect( myService.one ).toEqual(1);

        }))

    })

});

13
Hizmetinizin kendi bağımlılıkları olduğu zamanlara bir örnek görmek
isterim

2
Üzgünüm, aslında şöyle bir şey arıyordum: stackoverflow.com/q/16565531/295797
Roy Truelove

1
Servis beforeEachiçin birçok testin gerekli olduğu ... birçok durumda ... servisi enjekte etmenin iyi bir yolu var mı ? Bir veri modelini (hizmet) test etmek ve bir ton küresel değişken barındırmaktadır. Teşekkürler, C§
CSS

2
Neden (3) 'ün' doğru yol 'olduğunu
söylemiyorsunuz

2
@LeeGee Sanırım buna 'uygun' yol diyebiliriz çünkü özellikle test amacıyla orada bulunan ngMock AngularJS modülünü kullanıyor.
Robert

5

Yukarıdaki cevap muhtemelen gayet iyi çalışıyor olsa da (denemedim :)), sık sık çalıştırmam gereken daha çok test var, bu yüzden testleri kendim enjekte etmiyorum. Bunu () durumlarını açık bloklar halinde gruplayacağım ve enjeksiyonumu her bir açıklayıcı blokta beforeEach () veya beforeAll () içinde çalıştıracağım.

Robert, testleri hizmetten veya fabrikadan haberdar etmek için Angular $ enjektörü kullanmanız gerektiğini söylediği için de haklıdır. Angular, uygulamaya neyin mevcut olduğunu söylemek için bu enjektörü uygulamalarınızda da kullanır. Ancak, birden fazla yerde çağrılabilir ve ayrıca açık yerine örtük olarak da çağrılabilir . Aşağıdaki örnek spesifikasyon test dosyamda, beforeEach () bloğu , testlerin içinde atanacak şeyleri kullanılabilir hale getirmek için dolaylı olarak enjektörü çağırdığını fark edeceksiniz .

Bir şeyleri gruplamaya ve ön blokları kullanmaya geri dönersek, işte küçük bir örnek. Bir Cat Hizmeti yapıyorum ve onu test etmek istiyorum, bu nedenle Hizmeti yazmak ve test etmek için basit kurulumum şöyle görünecektir:

app.js

var catsApp = angular.module('catsApp', ['ngMockE2E']);

angular.module('catsApp.mocks', [])
.value('StaticCatsData', function() {
  return [{
    id: 1,
    title: "Commando",
    name: "Kitty MeowMeow",
    score: 123
  }, {
    id: 2,
    title: "Raw Deal",
    name: "Basketpaws",
    score: 17
  }, {
    id: 3,
    title: "Predator",
    name: "Noseboops",
    score: 184
  }];
});

catsApp.factory('LoggingService', ['$log', function($log) {

  // Private Helper: Object or String or what passed
    // for logging? Let's make it String-readable...
  function _parseStuffIntoMessage(stuff) {
    var message = "";
    if (typeof stuff !== "string") {
      message = JSON.stringify(stuff)
    } else {
      message = stuff;
    }

    return message;
  }

  /**
   * @summary
   * Write a log statement for debug or informational purposes.
   */
  var write = function(stuff) {
    var log_msg = _parseStuffIntoMessage(stuff);
    $log.log(log_msg);
  }

  /**
   * @summary
   * Write's an error out to the console.
   */
  var error = function(stuff) {
    var err_msg = _parseStuffIntoMessage(stuff);
    $log.error(err_msg);
  }

  return {
    error: error,
    write: write
  };

}])

catsApp.factory('CatsService', ['$http', 'LoggingService', function($http, Logging) {

  /*
    response:
      data, status, headers, config, statusText
  */
  var Success_Callback = function(response) {
    Logging.write("CatsService::getAllCats()::Success!");
    return {"status": status, "data": data};
  }

  var Error_Callback = function(response) {
    Logging.error("CatsService::getAllCats()::Error!");
    return {"status": status, "data": data};
  }

  var allCats = function() {
    console.log('# Cats.allCats()');
    return $http.get('/cats')
      .then(Success_Callback, Error_Callback);
  }

  return {
    getAllCats: allCats
  };

}]);

var CatsController = function(Cats, $scope) {

  var vm = this;

  vm.cats = [];

  // ========================

  /**
   * @summary
   * Initializes the controller.
   */
  vm.activate = function() {
    console.log('* CatsCtrl.activate()!');

    // Get ALL the cats!
    Cats.getAllCats().then(
      function(litter) {
        console.log('> ', litter);
        vm.cats = litter;
        console.log('>>> ', vm.cats);
      }  
    );
  }

  vm.activate();

}
CatsController.$inject = ['CatsService', '$scope'];
catsApp.controller('CatsCtrl', CatsController);

Spec: Kediler Denetleyicisi

'use strict';

describe('Unit Tests: Cats Controller', function() {

    var $scope, $q, deferred, $controller, $rootScope, catsCtrl, mockCatsData, createCatsCtrl;

    beforeEach(module('catsApp'));
    beforeEach(module('catsApp.mocks'));

    var catsServiceMock;

    beforeEach(inject(function(_$q_, _$controller_, $injector, StaticCatsData) {
      $q = _$q_;
      $controller = _$controller_;

      deferred = $q.defer();

      mockCatsData = StaticCatsData();

      // ToDo:
        // Put catsServiceMock inside of module "catsApp.mocks" ?
      catsServiceMock = {
        getAllCats: function() {
          // Just give back the data we expect.
          deferred.resolve(mockCatsData);
          // Mock the Promise, too, so it can run
            // and call .then() as expected
          return deferred.promise;
        }
      };
    }));


    // Controller MOCK
    var createCatsController;
    // beforeEach(inject(function (_$rootScope_, $controller, FakeCatsService) {
    beforeEach(inject(function (_$rootScope_, $controller, CatsService) {

      $rootScope = _$rootScope_;

      $scope = $rootScope.$new();
      createCatsController = function() {
          return $controller('CatsCtrl', {
              '$scope': $scope,
              CatsService: catsServiceMock
          });    
      };
    }));

    // ==========================

    it('should have NO cats loaded at first', function() {
      catsCtrl = createCatsController();

      expect(catsCtrl.cats).toBeDefined();
      expect(catsCtrl.cats.length).toEqual(0);
    });

    it('should call "activate()" on load, but only once', function() {
      catsCtrl = createCatsController();
      spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);

      // *** For some reason, Auto-Executing init functions
      // aren't working for me in Plunkr?
      // I have to call it once manually instead of relying on
      // $scope creation to do it... Sorry, not sure why.
      catsCtrl.activate();
      $rootScope.$digest();   // ELSE ...then() does NOT resolve.

      expect(catsCtrl.activate).toBeDefined();
      expect(catsCtrl.activate).toHaveBeenCalled();
      expect(catsCtrl.activate.calls.count()).toEqual(1);

      // Test/Expect additional  conditions for 
        // "Yes, the controller was activated right!"
      // (A) - there is be cats
      expect(catsCtrl.cats.length).toBeGreaterThan(0);
    });

    // (B) - there is be cats SUCH THAT
      // can haz these properties...
    it('each cat will have a NAME, TITLE and SCORE', function() {
      catsCtrl = createCatsController();
      spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);

      // *** and again...
      catsCtrl.activate();
      $rootScope.$digest();   // ELSE ...then() does NOT resolve.

      var names = _.map(catsCtrl.cats, function(cat) { return cat.name; })
      var titles = _.map(catsCtrl.cats, function(cat) { return cat.title; })
      var scores = _.map(catsCtrl.cats, function(cat) { return cat.score; })

      expect(names.length).toEqual(3);
      expect(titles.length).toEqual(3);
      expect(scores.length).toEqual(3); 
    });

});

Spec: Kedi Servisi

'use strict';

describe('Unit Tests: Cats Service', function() {

  var $scope, $rootScope, $log, cats, logging, $httpBackend, mockCatsData;

  beforeEach(module('catsApp'));
  beforeEach(module('catsApp.mocks'));

  describe('has a method: getAllCats() that', function() {

    beforeEach(inject(function($q, _$rootScope_, _$httpBackend_, _$log_, $injector, StaticCatsData) {
      cats = $injector.get('CatsService');
      $rootScope = _$rootScope_;
      $httpBackend = _$httpBackend_;

      // We don't want to test the resolving of *actual data*
      // in a unit test.
      // The "proper" place for that is in Integration Test, which
      // is basically a unit test that is less mocked - you test
      // the endpoints and responses and APIs instead of the
      // specific service behaviors.
      mockCatsData = StaticCatsData();

      // For handling Promises and deferrals in our Service calls...
      var deferred = $q.defer();
      deferred.resolve(mockCatsData); //  always resolved, you can do it from your spec

      // jasmine 2.0
        // Spy + Promise Mocking
        // spyOn(obj, 'method'), (assumes obj.method is a function)
      spyOn(cats, 'getAllCats').and.returnValue(deferred.promise);

      /*
        To mock $http as a dependency, use $httpBackend to
        setup HTTP calls and expectations.
      */
      $httpBackend.whenGET('/cats').respond(200, mockCatsData);
    }));

    afterEach(function() {
      $httpBackend.verifyNoOutstandingExpectation();
      $httpBackend.verifyNoOutstandingRequest();
    })

    it(' exists/is defined', function() {
      expect( cats.getAllCats ).toBeDefined();
      expect( typeof cats.getAllCats ).toEqual("function");
    });

    it(' returns an array of Cats, where each cat has a NAME, TITLE and SCORE', function() {
      cats.getAllCats().then(function(data) {
        var names = _.map(data, function(cat) { return cat.name; })
        var titles = _.map(data, function(cat) { return cat.title; })
        var scores = _.map(data, function(cat) { return cat.score; })

        expect(names.length).toEqual(3);
        expect(titles.length).toEqual(3);
        expect(scores.length).toEqual(3);
      })
    });

  })

  describe('has a method: getAllCats() that also logs', function() {

      var cats, $log, logging;

      beforeEach(inject(
        function(_$log_, $injector) {
          cats = $injector.get('CatsService');
          $log = _$log_;
          logging = $injector.get('LoggingService');

          spyOn(cats, 'getAllCats').and.callThrough();
        }
      ))

      it('that on SUCCESS, $logs to the console a success message', function() {
        cats.getAllCats().then(function(data) {
          expect(logging.write).toHaveBeenCalled();
          expect( $log.log.logs ).toContain(["CatsService::getAllCats()::Success!"]);
        })
      });

    })

});

DÜZENLEME Bazı yorumlara dayanarak cevabımı biraz daha karmaşık olacak şekilde güncelledim ve ayrıca Birim Testini gösteren bir Plunkr oluşturdum. Spesifik olarak, "Bir Denetleyicinin Hizmetinin kendisi $ log gibi basit bir bağımlılığa sahipse ne olur?" - test senaryoları örneğinde bulunan. Umarım yardımcı olur! Gezegeni Test Edin veya Hackleyin !!!

https://embed.plnkr.co/aSPHnr/


0

Başka bir yönerge, Google Places Autocomplete'i gerektiren bir yönergeyi test etmem gerekiyordu , onunla dalga geçmem gerekip gerekmediğini tartışıyordum ... yine de bu, gPlacesAutocomplete'i gerektiren yönerge için herhangi bir hata atarak işe yaradı.

describe('Test directives:', function() {
    beforeEach(module(...));
    beforeEach(module(...));
    beforeEach(function() {
        angular.module('google.places', [])
        .directive('gPlacesAutocomplete',function() {
            return {
                require: ['ngModel'],
                restrict: 'A',
                scope:{},
                controller: function() { return {}; }
             };
        });
     });
     beforeEach(module('google.places'));
});

-5

Bir kontrolörü test etmek istiyorsanız, aşağıdaki gibi enjekte edebilir ve test edebilirsiniz.

describe('When access Controller', function () {
    beforeEach(module('app'));

    var $controller;

    beforeEach(inject(function (_$controller_) {
        // The injector unwraps the underscores (_) from around the parameter names when matching
        $controller = _$controller_;
    }));

    describe('$scope.objectState', function () {
        it('is saying hello', function () {
            var $scope = {};
            var controller = $controller('yourController', { $scope: $scope });
            expect($scope.objectState).toEqual('hello');
        });
    });
});

2
soru denetleyici değil, test hizmeti ile ilgilidir.
Bartek S
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.