Viết unit test cho javascript với Jasmine – Phần 2

Tiếp nối phần 1, ở phần này mình sẽ giới thiệu một số chức năng nâng cao của Jasmine, giúp việc viết unit test được dễ dàng hơn.

Nếu chưa tải Jasmine về máy, các bạn nên đọc lại phần 1 để biết chỗ tải về và cách viết 1 số test case cơ bản. Nội dung bài viết lần này bao gồm:

  1. Một số matcher của Jasmine
  2. Cách dùng các hàm before, after
  3. Sử dụng spy và mock

1. Một số matcher của Jasmine

Matcher ở đây là 1 số hàm dùng để so sánh kết quả ta mong chờ (excepted value) và kết quả trả về từ hàm (return value). Trong các ví dụ ở bài trước, mình đã dùng matcher toBe, matcher đơn giản nhất để viết test case.

 it("Một với một là hai", function() {
   expect(2).toBe(cal.add(1,1));
 });

//Thêm not và trước matcher
 it("Một với một không phải là ba", function() {
 expect(3).not.toBe(cal.add(1,1));
 });

Ta còn có thể so sánh 2 object, sử dụng matcher toEqual:

 it("So sánh 2 object", function() {
    var foo = {
      a: 12,
      b: 34
    };
    var bar = {
      a: 12,
      b: 34
    };
    //foo == bar có kể quả false
    //Nhưng match toEqual vẫn so sánh đúng
    expect(foo).toEqual(bar);
 });

Một số matcher tiện lợi khác là: toBeDefined, toBeUndefined, toBeNull

  it("Sử dụng toBeDefined", function() {
     var a = {
        foo: "foo"
     };

     expect(a.foo).toBeDefined();
     expect(a.bar).not.toBeDefined();
  });

  it("Sử dụng toBeUndefined", function() {
      var a = {
        foo: "foo"
      };

     expect(a.foo).not.toBeUndefined();
     expect(a.bar).toBeUndefined();
  });

  it("Sử dụng toBeNull", function() {
    var a = null;
    var foo = "foo";

    expect(null).toBeNull();
    expect(a).toBeNull();
    expect(foo).not.toBeNull();
  });

2. Cách dùng các hàm before, after

Jasmine hỗ trợ 4 hàm before, after như sau

describe("Sử dụng before và after", function() {

 beforeEach(function() {
 //Hàm này được chạy NHIỀU LẦN vào đầu mỗi test case
 });

 afterEach(function() {
 //Hàm này được chạy NHIỀU LẦN vào cuối mỗi test case
 });

 beforeAll(function() {
 //Hàm này được chạy MỘT LẦN duy nhất trước khi chạy
 //các test case trong describe này
 });

 afterAll(function() {
 //Hàm này được chạy MỘT LẦN duy nhất sau khi chạy
 //xong các test case trong describe này
 });

});

3. Sử dụng spy và mock

Trong Jasmine, spy được dùng để kiểm tra xem 1 hàm có được gọi hay không. Ngoài ra, nó còn có thể được dùng để làm mock object (Sẽ giải thích kĩ ở dưới).

Ở ví dụ này, ta sử dụng spy để kiểm tra xem hàm eat đã được gọi hay chưa:

describe("Dùng spy", function() {
 var person, eaten = null;

 //Hàm này được chạy đầu mỗi test case
 beforeEach(function() {
  person = {
    eat: function(value) {
      eaten = value;
    }
  };

 //Gắn spy vào để theo dõi hàm eat của object person
  spyOn(person, 'eat');

  person.eat('banana');
 });

 it("tracks that the spy was called", function() {
   expect(person.eat).toHaveBeenCalled();
 });

 it("tracks all the arguments of its calls", function() {
   expect(person.eat).toHaveBeenCalledWith('banana');
 });

 it("stops all execution on a function", function() {
   //Spy sẽ chặn hàm eat, do đó giá trị eaten không được set
   expect(eaten).toBeNull();
 });
});

Khi cài đặt spy bằng hàm spy on, hàm được gọi tới đã bị chặn. Để hàm không bị chặn, ta setup thêm callThrough sau câu spy on

describe("Dùng spy", function() {
 var person, eaten = null;

 //Hàm này được chạy đầu mỗi test case
 beforeEach(function() {
  person = {
    eat: function(value) {
      eaten = value;
    }
  };

 //Gắn spy vào để theo dõi hàm eat của object person
 // Ở đây dùng thêm callThrough
  spyOn(person, 'eat').and.callThrough();

  person.eat('banana');
 });

 it("Not stop execution on a function", function() {
   //Spy không chặn hàm eat nữa
   expect(eaten).toBe('banana');
 });
});

Trong trường hợp hàm eat chưa được viết xong thì sao? Trong quá trình code, điều này vẫn thường xảy ra. VD như module A phụ thuộc vào module B, nhưng module B chưa được viết xong. Để giải quyết trường hợp này, ta tạo 1 module B giả, tạm hoạt động như module B để cho module A gọi. Module B giả này được gọi là mock.

Giả sử đối tượng person hàm foodEaten, nhưng viết chưa xong. Ta có thể dùng spy để làm mock, trả về giá trị cho hàm này

describe("Dùng spy", function() {
 var person, eaten = null;

 //Hàm này được chạy đầu mỗi test case
 beforeEach(function() {
    person = {
      eat: function(value) {
           eaten = value;
      },
      foodEaten: function(){
           //Viết chưa xong
      }
 };

 //Spy sẽ làm mock
 //Giả kết quả trả về của hàm foodEaten
     spyOn(person, 'foodEaten').and.callFake(function() {
        return 'banana';
     });;
 });

 it("stops all execution on a function", function() {
     //Gọi kết quả lấy từ hàm mock
     expect(person.foodEaten()).toBe('banana');
 });
});

Trong phạm vi bài viết mình không thể giới thiệu hết 1 số tính năng khác của Jasmine, các bạn chịu khó tự tìm hiểu nhé.

One thought on “Viết unit test cho javascript với Jasmine – Phần 2”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s