almost 4 years ago

上一次我們完成了酷炫的單頁式的 CRUD 應用,這次我們來介紹如何再 Rails 中為你的 AngularJS code 做 Unit Test。

我們承接上一次完成的 Project 繼續往下做,一起來寫 AngularJS 的測試吧!!

環境設置

Jasmine - Behavior-Driven JavaScript

AngularJS 的官網教學上就是使用這套 JavaScript Testing Framework 幫忙做測試。

既然有很多人用,就自然而然會有人將它整合到 Rails 中:jasmine-rails (Github Repo),在 Gemfile 中我們加上:

group :test, :development do
  gem 'jasmine-rails'
end

並安裝:

$ bundle install
$ rails generate jasmine_rails:install

guard

只要偵測到 Project 下的檔案被更動,它就會幫你執行已經寫好的 test,應該有不少人在使用吧。

Gemfile 裡剛剛增加的 group :test, :development 中加上:

gem 'guard'

並安裝:

$ bundle install
$ bundle exec guard init

guard-jasmine

裝上這個,guard 會自動去呼叫 jasmine 做測試,在 Gemfile 裡剛剛增加的 group :test, :development 中加上:

gem 'guard-jasmine', :git => 'https://github.com/guard/guard-jasmine', :branch => 'jasmine-2'

由於 Jasmine 更新到 2.0,我們使用這個套件在 Github 上的新版本配合它的 API。

接者我們在 routes.rb 中增加:

mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)

並且置換 Guardfile 中的:

guard :jasmine do

guard :jasmine, server: :webrick, server_mount: '/specs' do

angular-mocks

我們安裝 angular-mocks 來為 AngularJS 做測試,它是 AngularJS 的輔助測試工具,

Gemfile 中我們加上:

gem 'rails-assets-angular-mocks'
$ bundle install

開始寫測試

我們先在 spec/javascript 中增加這些檔案:

螢幕快照 2014-11-01 下午10.46.41.png

lib.js 中我們載入 angular-mocks 這個套件:

//= require angular-mocks

Unit Testing for indexCtrl

index_ctrl_spec.js 中,我們為 indexCtrl 來寫測試:

describe('indexCtrl', function(){

  beforeEach(module('languageApp'));

  it('should create "languages" model with empty array', inject(function($controller) {
    var scope = {},
        ctrl = $controller('indexCtrl', {$scope: scope});

    expect(scope.languages.length).toBe(0);
  }));

  it('should update "languages" model', inject(
    function($controller, $httpBackend) {

    var scope = {},
        ctrl = $controller('indexCtrl', {$scope: scope});

    $httpBackend.expectGET('/languages.json').respond([{name: 'Chinese', id: 1},
                                                       {name: 'English', id: 2}]);
    scope.update_languages();
    $httpBackend.flush();

    expect(scope.languages.length).toBe(2);
  }));
});

module('languageApp') 選定了 languageApp 這個 AngularJS App,

第一個 it 區塊中我們使用 $controller 來建立 indexCtrl 的物件,並測試他的建構是否正確

第二個 it 區塊中我們使用 $httpBackend ,造一個假介面給 $scope.update_languages() 中的 $http 使用,

並測試他是否正確執行這個 Request。

Unit Testing for listCtrl

list_ctrl_spec.js 中,我們為 listCtrl 來寫測試:

describe('listCtrl', function(){

  beforeEach(module('languageApp'));

  beforeEach(inject(function ($rootScope) {
    $rootScope.$apply(function() {
      $rootScope.update_languages = function() { /* nothing todo */};
      spyOn($rootScope, 'update_languages');
    });
  }));

  it('should call "update_languages" when listCtrl init', inject(
    function($controller, $rootScope) {
    
    var scope = $rootScope.$new(),
        ctrl = $controller('listCtrl', {$scope: scope});

    expect($rootScope.update_languages).toHaveBeenCalled();
  }));
});

我們要測試的是 listCtrl 在建構時,是否正確執行 Parent Controller 的 method update_languages()

因此我們在之前以 $rootScopespyOn 去 stub 這個 method,並在之後透過 toHaveBeenCalled() 檢查是否有被執行過。

Unit Testing for formCtrl

form_ctrl_spec.js 中,我們為 formCtrl 來寫測試:

describe('formCtrl', function(){

  beforeEach(module('languageApp'));

  beforeEach(inject(function ($rootScope) {
    $rootScope.$apply(function() {
      $rootScope.update_languages = function() { /* nothing todo */};
      spyOn($rootScope, 'update_languages');
    });
  }));

  it('should call "update_languages" with method completed', inject(
    function($controller, $rootScope) {
    
    var scope = $rootScope.$new(),
        ctrl = $controller('formCtrl', {$scope: scope});

    scope.completed({});

    expect($rootScope.update_languages).toHaveBeenCalled();
  }));
});

這檢查的內容裏跟剛剛在 listCtrl 中所做的差不多。

Unit Testing for itemCtrl

item_ctrl_spec.js 中,我們為 itemCtrl 來寫測試:

describe('itemCtrl', function(){

  beforeEach(module('languageApp'));

  beforeEach(inject(function ($rootScope) {
    $rootScope.$apply(function() {
      $rootScope.update_languages = function() { /* nothing todo */};
      $rootScope.language = {name: 'Chinese', id: 1};

      spyOn($rootScope, 'update_languages');
    });
  }));

  it('should change "isEdit" copy "onedit_language" with method "edit_toggle"', inject(function($controller, $rootScope) {
    var scope = $rootScope.$new(),
        ctrl = $controller('itemCtrl', {$scope: scope});
    scope.edit_toggle(true);

    expect(scope.isEdit).toBe(true);
    expect(scope.onedit_language).toEqual($rootScope.language);
  }));

  it('should call "update_languages" with method "update"', inject(
    function($controller, $rootScope, $httpBackend) {

    var scope = $rootScope.$new(),
        ctrl = $controller('itemCtrl', {$scope: scope});
    scope.edit_toggle(true);

    $httpBackend.expect('PUT', '/languages/1.json', $rootScope.language).respond('');
    scope.update();
    $httpBackend.flush();

    expect($rootScope.update_languages).toHaveBeenCalled();
  }));

  it('should call "update_languages" with method "delete"', inject(
    function($controller, $rootScope, $httpBackend) {

    var scope = $rootScope.$new(),
        ctrl = $controller('itemCtrl', {$scope: scope});

    $httpBackend.expect('DELETE', '/languages/1.json').respond('');
    scope.delete();
    $httpBackend.flush();

    expect($rootScope.update_languages).toHaveBeenCalled();
  }));
});

這裏雖然看起來做很多事,但是大部分的技巧剛剛已經提及,相信你也可以在有文件的狀況下寫出來。

測試看看吧!

在 Console 中執行:

$ guard

如果你看到的跟我一樣,恭喜你!你也會在 Rails 中為 AngularJS 寫測試囉!!

原始碼在這裏:Github Repo

← Rails 與 AngularJS 混搭風 - 2. 單頁式CRUD
 
comments powered by Disqus