Viblo CTF
+2

Sử dụng Jasmine và Karma với AngularJS

Bạn là một ruby-er. Bạn đã quen với việc viết test rspec, hãy thử một thứ tương tự với rspec nhưng là test cho javascript. Đó chính là Jasmine

Cài đặt

Cài đặt Karma

Trước hết bạn phải cài đặt nodejs và sử dụng npm để cài đặt Karma

sudo npm install karma -g --save-dev
sudo npm install karma-jasmine karma-chrome-launcher -g --save-dev

Tham số –save-dev lưu thông tin của karma vào khai báo devDependencies trong tệp package.json

Chạy thử server

./node_modules/karma/bin/karma start

Cài đặt Karma CommandLine Interface

sudo npm install karma-cli -g

Bây giờ muốn khởi động Karma, ta đơn giản chỉ cần

karma start

Jasmine

Jasmine là một framework để test code javascript theo mô hình BDD

Jasmine được chọn mặc định làm framework trong Karma. Để sử dụng Jasmine với Karma, chúng ta cần cài đặt gói thư viện karma-jasmine

Spec Suite là một bộ kịch bản test, bao gồm nhiều hàm, mỗi hàm test tập trung vào một nội dung cần kiểm tra của chương trình. Spec Suite được định nghĩa bởi hàm describe():

describe('Spec Suite: myController', function() {

    // các hàm khởi tạo

    // các hàm test
});

Chúng ta có thể phân nhỏ các Spec Suite bằng cách định nghĩa các Spec Suite lồng nhau:

describe('Spec Suite: myController', function() {

    describe('Sub Spec Suite: create function', function() {
        // ...
    });

    describe('Sub Spec Suite: update function', function() {
        // ...
    });
});

Khai báo hàm test (Spec)

Trong Jasmine, mỗi hàm kiểm thử được gọi là một Spec. Jasmine cung cấp hàm it() để định nghĩa Spec.

describe('Spec Suite: a very simple Spec', function() {
    it('contains a passing spec', function() {
        expect(true).toBe(true);
    });
});

Tương tự như Rspec của Rails, Jasmine cũng có berforeEach và afterEach để khai báo, xóa những biến, hàm sẽ được chạy trước (sau) khi chạy từng spec

describe('Spec Suite: myController', function() {

    beforeEach(function() {
        // khởi tạo các biến trước khi chạy spec ...
    });
});
describe('Spec Suite: myController', function() {

    afterEach(function() {
        // reset lại các biến môi trường ...
    });
});

Jasmine cũng cung cấp các hàm expect để khẳng định một biểu thức cần test phải có giá trị thỏa mãn một yêu cầu nào đó.

  • expect(x).toEqual(val): Khẳng định giá trị của đối tượng x bằng với val (nhưng không nhất thiết đồng nhất nhau).
  • expect(x).toBe(obj): Khẳng định rằng đối tượng x đồng nhất với obj (2 đối tượng này là một).
  • expect(x).toMatch(regexp): Khẳng định chuỗi x khớp với biểu thức chính quy regexp.
  • expect(x).toBeNull(): Khẳng định rằng biến x chứa giá trị là null.
  • expect(x).toBeTruthy(): Khẳng định rằng giá trị x là true hoặc ước lượng bằng true.
  • expect(x).toBeFalsy(): Khẳng định rằng giá trị x là false hoặc ước lượng bằng false.
  • expect(x).toContain(y): Khẳng định rằng x là một chuỗi ký tự và x chứa giá trị y (chuỗi y là một phần của chuỗi x).
  • expect(x).toBeGreaterThan(y): Khẳng định rằng x lớn hơn y.
  • expect(x).toBeDefined(): Khẳng định rằng biến x đã được định nghĩa.
  • expect(x).toBeUndefined(): Khẳng định rằng biến x chưa được định nghĩa.

Ví dụ đơn giản sử dụng Jasmine để test code Javascript

mkdir demo
cd demo
sudo npm install karma --save-dev
sudo npm install karma-jasmine karma-chrome-launcher --save-dev
karma init karma-unit.conf.js
mkdir test
mkdir test/unit

Tạo file test/unit/simpleSpec.js

describe("A very simple Unit testing", function () {

    var counter;

    beforeEach(function () {
        counter = 0;
    });

    it("Increments value", function () {

        counter++;

        expect(counter).toEqual(1);
    })

    it("Decrements value", function () {

        counter--;

        expect(counter).toEqual(-1);
    })
});

Bạn quay trở lại terminal, chạy lệnh và xem kết quả

karma start karma-unit.conf.js

alt

CHÚ Ý: Khi config karma, bạn phải điền đường dẫn đến thư mục các file js muốn được chạy

alt

Và server karma chạy ở chế độ real-time, sẽ chạy các file test ngay sau khi bạn chỉnh sửa và save lại

Áp dụng vào test AngularJS

Trước khi tìm hiểu cách áp dụng test jasmine cho AngularJS, chúng ta cần hiểu rõ một kỹ thuật quan trọng: kỹ thuật mocking.

Khai báo thư viện angular-mocks

Để làm việc này, chúng ta cần khai báo thư viện angular-mocks bằng cách bổ sung khai báo file angular-mocks.js trong cấu hình của Karma. Cần lưu ý thứ tự khai báo các file thư viện trong thuộc tính files của file cấu hình Karma:

//
// file: karma-unit.conf.js
//
module.exports = function(config) {
  config.set({

    // ...

    // list of files / patterns to load in the browser
    files: [
      'main/lib/angular/angular.js',
      'main/lib/angular/angular-mocks.js',
      'main/js/*.js',
      'test/**/*Spec.js'
    ],

    // .....

  });
};

Giả lập module ứng dụng

Chẳng hạn chúng ta cần test Service có tên myService trong module myApp, khi đó chúng ta cần sử dụng hàm angular.mock.module() tạo ngữ cảnh mock đối tượng cho myApp ứng với từng lệnh it():

describe('myApp Unit Testing', function() {

    // Mock our 'myApp' angular module
    beforeEach(angular.mock.module('myApp'));

    it('Unit testing 1', function() {
        // ...
    });

    it('Unit testing 2', function() {
        // ...
    });
});

Quá trình Unit Testing đòi hỏi phải cô lập thành phần đang được test khỏi các thành phần phụ thuộc khác. Hàm angular.mock.module() cho phép lấy service $provide ra để định nghĩa lại (thay thế) các service phụ thuộc khác trước khi chạy hàm test (it):

describe('myApp Unit Testing', function() {

    // Mock our 'myApp' angular module
    beforeEach(angular.mock.module('myApp', function($provide) {
        // sử dụng các hàm factory(), service(), value() của $provide
    }));

    it('Unit testing 1', function() {
        // ...
    });

    it('Unit testing 2', function() {
        // ...
    });
});

Để test các thành phần (Controller, Service, Filter, Directive), chúng ta cần tạo ra đối tượng cụ thể của thành phần đó, thay thế các dịch vụ phụ thuộc bằng các đối tượng mock, thực hiện các lời gọi hàm theo trình tự của kịch bản.

Để chỉ định thành phần nào sẽ được test,thư viện cung cấp hàm angular.mock.inject(). Ví dụ để test cho myController, code sẽ như sau

describe("myController Unit testing #1", function () {
    var mockScope;
    var controller;

    beforeEach(angular.mock.module("myApp"));

    beforeEach(angular.mock.inject(function ($rootScope, $controller) {
        mockScope = $rootScope.$new();
        controller = $controller("myController", {
            $scope: mockScope,
        });
    }));

    it("unit testing function", function () {
        // Test sử dụng biến controller và mockScope
    })
});

Sau đây chúng ta sẽ thử viết test cho một ứng dụng AngularJS đơn giản:

Tạo một Controller có tên myController thuộc ứng dụng myApp, thực hiện chức năng đơn giản là tăng giá trị cho biến counter mỗi khi người dùng bấm vào nút "Increment"

tạo file main/js/myController.js

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

app.controller('myController', ['$scope', 'backendService',
        function($ctrlScope, $backend) {
    $ctrlScope.counter = 0;

    $ctrlScope.incrementCounter = function() {
        $ctrlScope.counter += $backend.step();
    }

    $ctrlScope.resetCounter = function() {
        $ctrlScope.counter = $backend.init();
    }
}]);

Trong "myController" có sử dụng một Service "backendService", Service này vẫn chưa có. Khi đó, ta cần mock "backendService" khi test myController.

Tạo file test/unit/myController.Spec.js

describe("myController Unit testing #1", function () {

    // Arrange
    var mockScope;
    var controller;

    beforeEach(angular.mock.module("myApp"));

    beforeEach(angular.mock.inject(function ($controller, $rootScope) {
        mockScope = $rootScope.$new();

        controller = $controller("myController", {
            $scope: mockScope,
            backendService: {
                init: function() {
                    return 1;
                },
                step: function() {
                    return 5;
                },
                echo: function(msg) {
                    return 'echo[' + msg + ']';
                }
            }
        });
    }));

    it("Creates variable", function () {
        expect(mockScope.counter).toEqual(0);
    })

    it("Increments counter", function () {
        mockScope.incrementCounter();
        expect(mockScope.counter).toEqual(5);
    });

    it("Resets counter", function () {
        mockScope.resetCounter();
        expect(mockScope.counter).toEqual(1);
    });
});

Ra ngoài terminal và xem kết quả

alt

Source code https://github.com/linhnt/unit_test/


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.