Unit Test cho Nodejs với Mocha – Step by Step

0
450

Bài viết này mình sẽ hướng dẫn các bạn viết Unit test cho Nodejs bằng Mocha (một Javascript testing framework). Như các bạn cũng biết, việc Unit test cho một dự án là vô cùng quan trọng. Người ta đã phải bỏ công, bỏ sức ra để thiết kế những kiến trúc, mô hình để dự án có thể dễ Unit Test hơn. Điển hình như Android có mô hình MVP, web thì có MVC, MVVM…

Để cho bài viết dễ hiểu, mình sẽ thực hiện unit test cho một ứng dụng cụ thể. Đó là RESTful APIs tạo bởi Nodejs.

Chờ chút: Bạn đã nghe nói về Restful API chưa? Với Nodejs, việc tạo RESTful API cực đơn giản. Cách làm tại đây nhé: Tạo RESTful API đơn giản bằng Nodejs + MongoDB

Viết Unit Test cho Nodejs

#Unit Test là gì?

Unit Test là một cơ chế để kiểm tra từng hàm trong mã nguồn của bạn. Unit Test đảm bảo từng method trong mã nguồn hoạt động đúng yêu cầu, xử lý được các ngoại lệ, kiểm tra dữ liệu nhập vào và dữ liệu trả về có chính xác với yêu cầu hay không.

Nhiều bạn cứ nghĩ Unit Test thì do tester thực hiện nên hay bỏ qua kỹ năng này. Nhưng thực tế, Unit Test phải do developer thực hiện. Chỉ có dev mới hiểu và viết được chính xác các test case mà thôi.

Giờ chúng ta sẽ thực hiện viết Unit Test cho dự án Nodejs thông qua một ví dụ đơn giản nhé!

#Ví dụ viết Unit Test cho Nodejs

Ở bài viết này, để tập trung vào hướng dẫn viết unit test, mình đã chuẩn bị sẵn một sample code. Sample code này là một ứng dụng RESTFUL api đơn giản. Các bạn download về để thực hành nhé

>>Đọc thêm về git: Hướng dẫn sử dụng Github

Sau khi download source code về thì cần cài đặt thêm thư viện Mocha nữa

$ npm install -g mocha@2.3.1

1. Cấu trúc testing cho dự án NodeJs

Để tạo unit test cho dự án, bạn tạo thêm một thư mục Test trong dự án. Sau đó tạo thêm một file test. Ví dụ là: test-server.js

Đại khái, cấu trúc dự án bạn sẽ thấy như sau:

├── package.json
├── server
│   ├── app.js
│   ├── models
│   │   └── blob.js
│   └── routes
│       └── index.js
└── test
    └── test-server.js

Còn đây là cấu trúc một test case cơ bản:

it(<mô tả test case>, function(done) {
  //Gọi hàm để test  
});

Nếu bạn muốn nhóm nhiều test case lại thành một group thì có thể làm như sau:

// Ví dụ về nhóm nhiều test case thành một group
describe('Blobs', function() {
  it('should list ALL blobs on /blobs GET');
  it('should list a SINGLE blob on /blob/<id> GET');
  //... and more
});

2. Thêm thư viện cần thiết

Trong ví dụ này, mình sẽ dụng thư viện Chai (v3.2.0) và chai-http (v 1.0.0) để tạo kết nối HTTP sau đó sẽ test kết quả trả về.

Cài đặt:

$ npm install chai@3.2.0 chai-http@1.0.0 --save-dev

Sau đó update lại file test-server.js như sau:

var chai = require('chai');
var chaiHttp = require('chai-http');
var server = require('../server/app');
var should = chai.should();

chai.use(chaiHttp);

describe('Blobs', function() {
  it('should list ALL blobs on /blobs GET');
  it('should list a SINGLE blob on /blob/<id> GET');
  it('should add a SINGLE blob on /blobs POST');
  it('should update a SINGLE blob on /blob/<id> PUT');
  it('should delete a SINGLE blob on /blob/<id> DELETE');
});

OK, về cơ bản là chúng ta đã hiểu và chuẩn bị xong, giờ bắt đầu viết test case.

3. Viết test case cho các hàm GET

Các bạn update lại test case đầu tiên như sau:

it('should list ALL blobs on /blobs GET', function(done) {
  chai.request(server)
    .get('/blobs')
    .end(function(err, res){
      res.should.have.status(200);
      done();
    });
});

Chúng ta truyền vào hàm ẩn danh (callback) một tham số done (thực chất nó là một hàm). Tham số này sẽ kết thúc test case khi được gọi.

Về ví dụ test case trên thì rất dễ hiểu. Nó đơn giản là chúng ta sẽ tạo một request (GET) tới  API URL: “/blobs”. Sau đó kiểm tra xem mã trả về có phải có status là 200 hay không!?

Cũng dễ hiểu phải không?

Để chạy test case, chúng ta gọi chạy lệnh mocha

$ mocha
  Blobs
Connected to Database!
GET /blobs 200 19.621 ms - 2
    ✓ should list ALL blobs on /blobs GET (43ms)
    - should list a SINGLE blob on /blob/<id> GET
    - should add a SINGLE blob on /blobs POST
    - should update a SINGLE blob on /blob/<id> PUT
    - should delete a SINGLE blob on /blob/<id> DELETE

  1 passing (72ms)
  4 pending

Hmm. Nếu chỉ Unit test mỗi status thì cũng không có nhiều ý nghĩa lắm. Thử kiểm tra thêm vài yếu tố nữa xem sao.

it('should list ALL blobs on /blobs GET', function(done) {
  chai.request(server)
    .get('/blobs')
    .end(function(err, res){
      res.should.have.status(200);
      res.should.be.json;
      res.body.should.be.a('array');
      done();
    });
});

Viết mấy test case này cũng đơn giản nhỉ. Đọc code cũng hình dung được ý định của test case, giống như viết văn vậy.

4. Viết test case cho các hàm POST

Bạn xem lại mã nguồn của ứng dụng, khi có một “blob” được thêm mới , sẽ thông báo cho client biết với nội dụng như dưới:

{
  "SUCCESS": {
    "__v": 0,
    "name": "name",
    "lastName": "lastname",
    "_id": "some-unique-id"
  }
}

OK, suy nghĩ một lúc về kết quả mong muốn và viết test case thôi.

it('should add a SINGLE blob on /blobs POST', function(done) {
  chai.request(server)
    .post('/blobs')
    .send({'name': 'Java', 'lastName': 'Script'})
    .end(function(err, res){
      res.should.have.status(200);
      res.should.be.json;
      res.body.should.be.a('object');
      res.body.should.have.property('SUCCESS');
      res.body.SUCCESS.should.be.a('object');
      res.body.SUCCESS.should.have.property('name');
      res.body.SUCCESS.should.have.property('lastName');
      res.body.SUCCESS.should.have.property('_id');
      res.body.SUCCESS.name.should.equal('Java');
      res.body.SUCCESS.lastName.should.equal('Script');
      done();
    });
});
Note: Nếu bạn muốn hiểu thực chất test case này đang kiểm tra thế nào, kết quả thực trả về như nào thì đơn giản là thêm console.log(res.body) vào trước assert đầu tiên.

5. Hooks

Cho đến thời điểm này, các bạn có để ý là với các test case, chúng ta đều tương tác trực tiếp với database không? Thực tế, nếu làm như vậy sẽ làm hỏng dữ liệu test của chúng ta.

Do đó, với unit test, chúng ta nên fake luôn cả dữ liệu với mỗi test case nếu cần. Để dễ hiểu, với test case cho hàm POST, thay vì gọi tới API POST và lưu một blob vào database. Chúng ta sẽ fake việc lưu vào database, và trả kết quả như bình thường.

Để làm được điều này, chúng ta có một số hàm như beforeEach() and afterEach() hooks. Đúng như tên gọi của nó, những này cho phép chúng ta thực hiện một số tác vụ chuẩn bị, kết thúc khi gọi hàm để test.

Chúng ta thử nhé!

Đầu tiên, mình sẽ thêm một file _config.js vào “server” folder, để tách URI tương tác với database, phục vụ có unit test.

var config = {};

config.mongoURI = {
  development: 'mongodb://localhost/node-testing',
  test: 'mongodb://localhost/node-test'
};

module.exports = config;

Sau đó, cập nhật lại app.js để cấu hình môi trường ứng dụng. Ý là, với môi trường developement thì tương tác với database này, còn khi thành product thì sẽ tương tác với database khác.

// *** config file *** //
var config = require('./_config');

// *** mongoose *** ///
mongoose.connect(config.mongoURI[app.settings.env], function(err, res) {
  if(err) {
    console.log('Error connecting to the database. ' + err);
  } else {
    console.log('Connected to Database: ' + config.mongoURI[app.settings.env]);
  }
});

Cuối cùng là viết unit test:

process.env.NODE_ENV = 'test';

var chai = require('chai');
var chaiHttp = require('chai-http');
var mongoose = require("mongoose");

var server = require('../server/app');
var Blob = require("../server/models/blob");

var should = chai.should();
chai.use(chaiHttp);


describe('Blobs', function() {

  Blob.collection.drop();

  beforeEach(function(done){
    var newBlob = new Blob({
      name: 'Bat',
      lastName: 'man'
    });
    newBlob.save(function(err) {
      done();
    });
  });
  afterEach(function(done){
    Blob.collection.drop();
    done();
  });

Bây giờ thì trước mỗi test case, database được clear, thêm một blob. Khi test case kết thúc thì lại xóa database trước khi sang test case khác.

Ok, giờ bạn thử chạy lại mocha xem có pass hết các test case không nhé.

Với các test case cho hàm UPDATE và DELETE, hoàn toàn tương tự như POST nhé. Các bạn có thể tham khảo thêm trong source code nhé.

#Tổng kết

Như vậy mình đã kết thúc phần chia sẻ những kiến thức bản để viết unit test cho Nodejs sử dụng thư viện mocha.

Mình nhắc lại là việc viết unit test không khó nhưng lại rất quan trọng và lại rất hay được các bạn cố tình quên.

Mời các bạn đọc thêm bài viết về Nodejs nữa tại đây:

Hi vọng bài viết này sẽ giúp các bạn cảm thấy thoải mái với unit test hơn. Chúc các bạn code vui vẻ nhé!

Bình luận. Đặt câu hỏi cũng là một cách học

avatar
  Theo dõi bình luận  
Thông báo