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 ...
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
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
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 $$rovide 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ả
Source code https://github.com/linhnt/unit_test/