Angular JS, Jasmine ve Karma ile Unit Test – Filter Testi

Merhaba,

Angular ile bir aplikasyon yapıyorsunuz ve filter(filtre) yazdınız. Hadi bakalım bunun unit testini yazın. Hemen şöyle tarih formatlayan bir filtremiz olsun:

 
 
  1. angular.module('moduleName',[]).filter('utcDate',function() {
  2.    return function(input){
  3.        if (input === null) {
  4.            return '';
  5.        }
  6.        return moment(moment.utc((new Date(input))).toDate()).format('DD.MM.YYYY HH:mm');
  7.    };
  8. });

Test yazarken tüm branch’lere (her if-else) girmemiz gerekecek dolayısıyla da burada bir null değerle bir de normal bir tarih değeriyle iki input verirsek tüm kodu cover etmiş oluruz.

 
 
  1. describe('Utc date filter test suite: ',function(){
  2.     var mockfilter;
  3.     beforeEach(function () {
  4.        module('moduleName');
  5.         inject(function (utcDateFilter) {
  6.             mockfilter = utcDateFilter;
  7.         });
  8.     });
  9.     it('Should convert properly', function () {
  10.         var d = "Fri Mar 11 2016 09:49:00 GMT+0200 (EET)",
  11.             exp = "11.03.2016 09:49";
  12.         expect(mockfilter(d)).toBe(exp);
  13.     });
  14.     it('Should return empty value if input is null', function () {
  15.        expect(mockfilter(null)).toBe('');
  16.     });
  17. });

 

Burada dikkat etmemiz gerekn “utcDate” diye yazdığımız filtreyi inject ederken “utcDateFilter” diye çağırmamız.

Kolay Gelsin

Angular JS, Jasmine ve Karma ile Unit Teste – Giriş

AngularJs yapısı gereği unit test’e uygun bir yapısı vardır. Bu uygunluğun nedeni de “Loose coupling” ve “Dependency Injection” felsefelerini benimsemiş olması. Yani içerdiği elemanların birbirine bağımlılığı yok.
Zaten angularjs ile test işlerine girmişseniz bu konulara aşinasınızdır daha fazla uzatmadan nasıl bir yol izleyeceğiz onu anlatayım. Tabii bazı varsayımlarım var. Birincisi modern bir web aplikasyonu yazdığınızı yani npm,bower gibi güçlü paket yöneticileri, grunt gibi bir task işleyicisi, karma gibi de bir test koşucunuz oldugunu ve ilk paragrafta bahsettiğim gibi DI bir mimariniz varsa (zaten olmasa yapmanız zor olacaktır işin içinden çıkamayacağınız bir durum alacaktır: aynen böyle olursunuz)

Malzemeler

  • Karma
  • Phantomjs (headless browser)
  • Cobertura (coverage için)
  • Bower
  • Npm
  • Jasmine

Tarif

Öncelikle daha önce de dediğim gibi bazı beklentilerim var. Projenizde npm ve bower paket yöneticisi olmalı. Bunlar projenizde eksik ise özellikle npm’i apt-get install … komutu  ile kolayca kurabiliriz. Dolayısıyla elimizde bir package.json dosyası yani projemize yüklediğimiz paketler olmalı. Örnek olarak şöyle birşey:

package.json
 
{
  "name": "xxxx",
  "version": "1.0.0",
  "description": "xxxxxx",
  "author": "sercan",
  "private": true,
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-karma": "^0.12.1",
    "jasmine-core": "^2.3.4",
    "karma": "^0.13.14",
    "karma-chrome-launcher": "^0.2.1",
    "karma-coverage": "^0.5.3",
    "karma-firefox-launcher": "^0.1.7",
    "karma-html-reporter": "^0.2.7",
    "karma-jasmine": "^0.3.6",
    "karma-junit-reporter": "^0.3.8",
    "karma-phantomjs-launcher": "^0.2.1"
  }
}

 

Tabii test için ve lokalde kullanılacak paketler -dev parameteresiyle kurulmuştur. Buradaki bazı kütüphaneler olur da ihtiyacınız olur diye var (Ör: junit reporter, chrome-launcer vb).

İkinci paket yöneticimiz (yani bower) ile angular, bootstrap gibi kütüphaneleri kuruyoruz. Ekstra olarak yine bir development dependency olan angular-mocks kütüphanesini indiriyoruz. Bu bizim angular objelerini mock’lamak için yani sanal muadilini yaratmak için kullanacağımız kütüphane. Onu da bower install -dev  …  şeklinde kurabildiğinizi düşünüyorum.

Şimdi geldik karma’yı ayarlamaya. Anlatmaya direk dosyanın üzerinden devam ediyorum:

karma
 
  1. module.exports = function(config) {
  2.     config.set({
  3.         // base path
  4.         basePath: './',
  5.         // Kullanılacak framework, jasmine kullanıyoruz
  6.         frameworks: ['jasmine'],
  7.         files: [
  8.             'bower_components/angular/angular.js',
  9.             'bower_components/jquery/dist/jquery.min.js',
  10.             'bower_components/angular-route/angular-route.js',
  11.             'bower_components/angular-mocks/angular-mocks.js',
  12.             //...vs yüklenmesi gereken dosyalar
  13.         ],
  14.         // Plugin'ler
  15.         plugins: [
  16.        'phantomjs',
  17.        'karma-jasmine',
  18.        'karma-phantomjs-launcher',
  19.        'karma-coverage',
  20.        'karma-chrome-launcher',
  21.        'karma-firefox-launcher',
  22.        'karma-html-reporter',
  23.        'karma-junit-reporter'
  24.    ],
  25.         // Dışlanacak dosyalar, yani test'e dahil etmek istemediğimiz
  26.         exclude: [
  27.         ],
  28.         // Raporlama için kullanacağımız pluguin ler
  29.    reporters: ['progress', 'html', 'coverage', 'junit'],
  30.    // Junit için ayarlar
  31.    junitReporter: {
  32.       outputDir: 'xxx/junit', // output path
  33.       outputFile: 'junit-test-report.xml', //dosya ismi, path ile birleşecek
  34.       suite: '', //çıkan xml de suite için olan attribute'u ayarlar
  35.       useBrowserName: false // adından anlaşılıyor
  36.    },
  37.    // Kod coverage için cobertura kullanıyoruz, onun ayarları
  38.    coverageReporter: {
  39.       reporters: [
  40.           {
  41.          type : 'cobertura',
  42.          dir : 'xxx/coverage'
  43.           },
  44.           {
  45.          type : 'html',
  46.                    dir : 'xxx/coverage'
  47.           }
  48.       ]
  49.    },
  50.    // Karma html reporter ayarları
  51.    htmlReporter: {
  52.       outputDir: 'xxx/karma_html',
  53.       templatePath: null,
  54.       focusOnFailures: true,
  55.       namedFiles: false,
  56.       pageTitle: null,
  57.       urlFriendlyName: false,
  58.       reportName: ''
  59.    },
  60.         // web server port
  61.         port: 9876,
  62.         // renklendirme
  63.         colors: true,
  64.         // logging leveli
  65.         logLevel: config.LOG_INFO,
  66.         // dosyalar değiştiğinde çalışsın mı
  67.         autoWatch: true,
  68.         // Hangi browser'da test yapalım
  69.         browsers: ['PhantomJS'],
  70.         // Timeout zamanı
  71.         captureTimeout: 60000,
  72.         // Tek kosumluk test ayarı
  73.         singleRun: false
  74.     });
  75. };

 

zaten gördüğünüz üzere bu karma.conf.js dosyası bir node js modülü. İçinde de belirli ayarlar var.

Detaylı bilgiye buradan ulaşabilirsiniz: https://karma-runner.github.io/0.13/config/configuration-file.html

Şimdi basit bir test yazmaya hazırız fakat öncelikle örnek bir kodumuz olmalı. Biz öncelikle en basitinden başlayalım: controller testi

loginController.js adında bir kontrolumuz olsun:

loginController.js
 
  1. angular.module('module_name',[]).controller('loginController', ['$scope', function ($scope) {
  2.         $scope.validForm = true;
  3.         $scope.User = {Username: '', Password: ''};
  4.         $scope.submitButtonText = "Sign In";
  5.         $scope.alerts = [];
  6.         $scope.loading= false;
  7.         $scope.closeAlert = function (index) {
  8.             $scope.alerts.splice(index, 1);
  9.         };
  10.     }]);

Beginner’lar için açıklıyayım module_name adlı modulumuzun altında loginController adlı bir controller’ımız var ve $scope’a bağımlı. İçinde closeAlert diye bir method var. Diğer satırlarda bazı değişkenler var.

Şimdi jasmine framework ile test yazacağız. Ama ondan önce isterseniz şu linke bir bakın: http://jasmine.github.io/2.4/introduction.html

Genel bir jasmine test template’i şu şekildedir:

 
 
  1. describe("Test spec tanımı. Ör: login controller testleri", function() {
  2.   beforeEach(function() {
  3.      //kod
  4.   });
  5.   afterEach(function() {
  6.     //kod
  7.   });
  8.   it("Test tanımı", function() {
  9.     //assertion
  10.   });
  11.   it("Another test", function() {
  12.     //assertion
  13.   });
  14. });

 

Biz de bu yapı da birşey yapacağız ama işin içinde bir trick var. O da modülü mock’lamak olacaktır ve controller’dan başka bir scope’da yeni bir muadil yaratmak olacaktır. Tabii ki angular-mock kutuphanesini kullanarak. Ondan sonra da testleri yazacağız.

 
 
  1. describe('Login testing suite', function () {
  2.     var mockLoginController, scope, apiFactoryMock;
  3.     //Burada Inject olaylarını gerçekleştiriyoruz
  4.     beforeEach(function () {
  5.         //Modulu mock'layıp yaratıyoruz
  6.         angular.mock.module('module_name');
  7.         //controller'ı inject edip global bir değişkene atıyoruz
  8.         angular.mock.inject(function ($controller, $rootScope) {
  9.             //Yeni scope yaratılıyor
  10.             scope = $rootScope.$new();
  11.             //Controller yaratılıyor
  12.             mockLoginController = $controller('loginController', {$scope: scope});
  13.         });
  14.     });
  15.     it('Should "validForm" parameter be defined', function () {
  16.         expect(scope.validForm).toBeDefined();
  17.     });
  18.     it('Should "User" model be defined', function () {
  19.         expect(scope.User).toBeDefined();
  20.     });
  21.     it('Should close alert func. works properly', function () {
  22.         scope.alert=[1,2,3,4];
  23.         mockLoginController.closeAlert(2);
  24.         expect(scope.alert.size).toBe(3);
  25.     });
  26. //Şeklinde testlerimiz sürer gider
  27. });

Bundan sonraki anlatımlarda direk çözüme yönelik testler yazacağız. Ör: filter,directive, timeout func. vb.

Esen kalın.