+2

Giới thiệu về Jest JavaScript Testing Framework

Giới thiệu về Jest

Jest là JavaScript Testing Framework được tạo bởi Facebook do Christoph Nakazawa thiết kế và xây dựng. Nó hoạt động với các dự án sử dụng Babel, TypeScript, Node, React, Angular, Vue ...

Cài đặt

Cài đặt jest khá đơn giản

Sử dụng yarn

yarn add --dev jest

Sử dụng npm

npm install --save-dev jest

Tiếp theo, hãy tạo 1 file có tên là sum.js với nội dụng

function sum(a, b) {
  return a + b;
}
module.exports = sum;

Sau đó, là file sum.test.js để kiểm tra kết quả của function sum

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Tiếp đến, thêm dòng sau vào file package.json

{
  "scripts": {
    "test": "jest"
  }
}

Cuối cùng. hãy chạy lệnh yarn test hoặc npm run test ta được kết quả như sau :

PASS  ./sum.test.js
✓ adds 1 + 2 to equal 3 (5ms)

Chúc mừng mọi người, ví dụ test của bạn bằng jest đã thành công.

Using Matchers

Common Matchers

Cách đơn giản nhất để kiểm tra giá trị, ta đến với vị dụ sau đây :

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

Ở đoạn code trên toBe(4) là 1 matcher. Tuy nhiên, khi sử dụng để so sánh giữa các object với nhau thì hãy sử dụng toEqual.Vì toBe sử dụng === để so sánh và đưa ra kết quả.Mà như chúng ta đã biết với js

a = { master: 10 };
b = { master: 10 };
a === b
===> false

Vậy nên khi so sánh các object, mảng thì ta nên sử dụng toEqual, ta có ví dụ, mọi người hãy làm thử để xem kết quả như nào nhé :

test('object assignment', () => {
  const data = {one: 1};
  data['two'] = 2;
  expect(data).toEqual({one: 1, two: 2});
});

Ngoài ra, ta còn có thể sử dụng not.ToBe() ( ngược lại với trường hợp toBe() )

test('adding positive numbers is not zero', () => {
  for (let a = 1; a < 10; a++) {
    for (let b = 1; b < 10; b++) {
      expect(a + b).not.toBe(0);
    }
  }
});

Truthiness

Ở một số trường hợp, chúng ta cần phân biệt giữa null,undefined,falsenhưng đôi khi bạn không muốn xử lý chúng theo cách khác nhau. Jest chứa những function cho phép bạn trả về những kết quả bạn muốn.

toBeNull hàm so sánh với giá trị null.

toBeUndefined hàm so sánh với giá trị undefined.

toBeDefined là hàm cho kết quả ngược lại toBeUndefined.

toBeTruthy so sánh với giá trị true.

toBeFalsy so sánh với giá trị false.

Dưới đây là một số ví dụ :

test('null', () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});

test('zero', () => {
  const z = 0;
  expect(z).not.toBeNull();
  expect(z).toBeDefined();
  expect(z).not.toBeUndefined();
  expect(z).not.toBeTruthy();
  expect(z).toBeFalsy();
});

Numbers

Jest hỗ trợ hầu hết các phép so sánh giữa các số.

test('two plus two', () => {
  const value = 2 + 2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeGreaterThanOrEqual(3.5);
  expect(value).toBeLessThan(5);
  expect(value).toBeLessThanOrEqual(4.5);

  // toBe and toEqual are equivalent for numbers
  expect(value).toBe(4);
  expect(value).toEqual(4);
});

Trường hợp, số thập phân ta nên sử dụng toBeCloseTo thay vì toEqual

test('adding floating point numbers', () => {
  const value = 0.1 + 0.2;
  //expect(value).toBe(0.3);           trường hợp này bị lỗi do làm tròn
  expect(value).toBeCloseTo(0.3); // trường hợp này đúng.
});

Strings

Bạn có thể kiểm tra một đoạn văn bản với regular expressions bằng toMatch:

test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
  expect('Christoph').toMatch(/stop/);
});

Arrays and iterables

Bạn có thể sử dụng toContain để kiểm tra phần tử có thuộc Arrays hoặc iterables :

const shoppingList = [
  'diapers',
  'kleenex',
  'trash bags',
  'paper towels',
  'milk',
];

test('the shopping list has milk on it', () => {
  expect(shoppingList).toContain('milk');
  expect(new Set(shoppingList)).toContain('milk');
});

Exceptions

Nếu bạn muốn kiểm tra bất cứ hàm nào phát sinh ra lỗi khi được gọi, hãy sử dụng toThrow :

function compileAndroidCode() {
  throw new Error('you are using the wrong JDK');
}

test('compiling android goes as expected', () => {
  expect(() => compileAndroidCode()).toThrow();
  expect(() => compileAndroidCode()).toThrow(Error);

  // You can also use the exact error message or a regexp
  expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
  expect(() => compileAndroidCode()).toThrow(/JDK/);
});

Testing Asynchronous Code

Callbacks

test('the data is peanut butter', done => {
  function callback(data) {
    try {
      expect(data).toBe('peanut butter');
      done();
    } catch (error) {
      done(error);
    }
  }

  fetchData(callback);
});

Promises

Nếu code của bạn có sử dụng promises, ta vẫn có thể xử lý nó với jest

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

Async/Await

Ngoài ra bạn có thể sử dụng async, await trong các test case của mình.Dưới đây là ví dụ cụ thể.

test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

Bạn cũng có thể sử dụng async, awai cùng .resolves,.rejects

test('the data is peanut butter', async () => {
  await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  await expect(fetchData()).rejects.toMatch('error');
});

Setup and Teardown

Thông thường, bạn cần phải thiết lập một số yếu tố trước khi chạy các test.Jest cũng cung cấp các chức năng trợ giúp để xử lý việc này.

Repeating Setup For Many Tests

Đối với những trường hợp, việc kiểm tra cần lặp đi lặp lại nhiều lần.Nếu bạn cần phương thức để thiết lập trước và sau mỗi lần kiểm tra thì hãy tham khảo ví dụ dưới đây.Bạn có thể sử dụng beforeEachafterEach.

beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

One-Time Setup

Một số trường hợp xảy ra, bạn chỉ cần thiết lập một lần duy nhất. Jest cung cấp beforeAllafterAll để xử lý tình huống này.

beforeAll(() => {
  return initializeCityDatabase();
});

afterAll(() => {
  return clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

Scoping

Mặc định, beforeafter được sử dụng đối với tất cả các trường hợp test trong file. Như vậy ,đối với trường hợp bạn chỉ muốn thiết lập cho một nhóm các trường hợp test thì ta có thể sử dụng describe để xử lý.

// Applies to all tests in this file
beforeEach(() => {
  return initializeCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

describe('matching cities to foods', () => {
  // Applies only to tests in this describe block
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 veal', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});

Mock Functions

Using a mock function

Để minh họa cho việc mock function ta có ví dụ sau :

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);

Mock Return Values

Bạn cũng có thể mock các giá trị trả về của hàm

const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

Mocking Modules

// users.js
import axios from 'axios';

class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}

export default Users;
// users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

Mock Implementations

const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true

// foo.js
module.exports = function () {
  // some implementation;
};

// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42
const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

Custom Matchers

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();

// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

Và tất cả những gì ở trên là những phần tổng quan về jest mà mình tìm hiểu được, hy vọng bài chia sẻ của mình giúp các bạn có thể vận dụng jest theo mục đích của mình 😃 .


All Rights Reserved

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