Hướng dẫn viết unit test React với Jest và Enzyme (P1)

1. Front-end unit test

  • Dạo quanh 1 vòng blog của công ty thì ta thấy các vấn đề chủ yếu xoay quanh về coding là chính, unit testing đã ít, front-end unit testing còn ít hơn.
  • Hôm nay cùng tìm hiểu về unit testing - cụ thể là áp dụng Jest và Enzyme để thực hiện unit test cho ReactJS.

1.1. Jest

  • Jest được tạo bởi facebook và là 1 frameword dùng để test javascript.
  • Jest là 1 cái tên quen thuộc, thường được cài đặt sẵn vào khi tạo cả project reactjs lẫn project react-native nghĩa là chúng ta không cần cài đặt hoặc config gì thêm nữa và đặc biệt nó hiện đang có 35.3k sao.
  • Jest đơn giản và dễ dàng sử dụng, document rõ ràng cụ thể. Các bạn có thể tham khảo thêm ở đây

1.2. Enzyme

  • Enzyme - thư viện được phát triển bởi nhà Airbnb
  • Lí do sử dụng enzyme:
    • API của Enzyme có nghĩa là trực quan và linh hoạt bằng cách bắt chước API của jQuery để thao tác và truyền tải DOM.
  • shallow renderingmount rendering. Bạn có thể tìm hiểu thêm.
    ở đây. Bạn có thể hiểu là shallow rendering dùng khi mà bạn không muốn render componet con và khi bạn muốn render cả component con thì bạn dùng mount rendering
  • Có 19.6k sao trên github.

2. Set up a React application

  • Trước hết thì bạn cần Node 6+ và yarn hoặc npm được cài đặt trên máy bạn.

Bước 1: Tạo 1 project

  npx create-react-app counter-app
  • Như đã nói trên thì khi khởi tạo project thì cài luôn jest vào project và có thể chạy yarn test

Bước 2: Cài đặt và config enzyme

  yarn add enzyme enzyme-adapter-react-16 --dev
  • Ta cần tải thêm adapter tương ứng với phiên bản react mình đang sử dụng. Nếu bạn sử dụng react 17 thì dùng yarn add @wojtekmaj/enzyme-adapter-react-17
  • Thêm config bằng cách sửa file setupTests.js. Việc để chúng ta báo vs jest và enzyme rằng chúng ta sẽ sử dụng adapter nào.
 import { configure } from 'enzyme';
 import Adapter from 'enzyme-adapter-react-16';
    
 configure({ adapter: new Adapter() });
  • Với create-react-app thì đã cài đặt sẵn sẽ chạy file này trước bất kỳ test nào.

  • Sửa file App.js

import React, { useState } from 'react';

const App = () => {
  const [counter, setcounter] = useState(0);

  return (
    <div>
      <h1>This is counter app</h1>
      <div className="counter-value">Count: {counter}</div>
      <button className="increment" onClick={() => setcounter(counter + 1)}>
        Increment
          </button>
      <button className="decrement" onClick={() => setcounter(counter - 1)}>
        Decrement
          </button>
    </div>
  );
}

export default App;

Bước 3: Test

  • Chúng ta đến src/App.test.js và sửa
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
    
describe('App component', () => {
  //Test 1: Ví dụ test quản lý state cơ bản
  it('starts with a count of 0', () => {
    const wrapper = shallow(<App />);
    const text = wrapper.find('div.counter-value').text();
    expect(text).toEqual('Count: 0');
  });
  //Test 2: Ví dụ test tương tác của người dùng
  it('increments count by 1 when the increment button is clicked', () => {
    const wrapper = shallow(<App />);
    const incrementBtn = wrapper.find('button.increment');
    incrementBtn.simulate('click');
    const text = wrapper.find('div.counter-value').text();
    expect(text).toEqual('Count: 1');
  });

In the above snippet, the shallow render of our App component is stored in the wrapper variable. We then grab the text inside the p tag within the component’s output and check if the text is the same was what we passed into the toEqual matcher function.

  • Đoạn Test 1 chúng ta thấy, shallow render của App component được lưu lại vào 1 wrapper variable. Rồi dùng API của enzyme để tìm div có classname là counter-value và lấy text ra so sánh bằng hàm toEqual.

  • Đoạn Test 2 khá giống đoạn Test 1 nhưng có thêm incrementBtn.simulate('click');. simulate() function có tác dụng mô phỏng lại 1 số DOM event và ở đây mình đã mô phỏng lại click và state bây giờ sẽ là 1.

  • Khi chạy yarn test thì chúng ta có kết quả thế này

-Nhưng để ý ở đây chúng ta đang gọi const wrapper = shallow(<App />); 2 lần mà nguyên tắc nếu gọi quá 1 lần thì dùng biến chung. Chúng ta sửa đoạn code như sau.

...
describe('App component', () => {
  let wrapper;
  beforeEach(() => {
      wrapper = shallow(<App />);
  });
...
  • Và tương tự cũng sẽ afterEach.

  • Tiếp theo ta sẽ xem qua về việc test react component bằng snapshot, 1 chức năng của jest.

Snapshots testing

  • Snapshot testing giúp chúng ta có thể kiểm tra được kết quả các lần render của 1 component là luôn giống nhau. Khi chạy lần đầu Jest sẽ render React component đang được test và lưu output lại trong 1 file JSON và ở các lần sau Jest sẽ check xem rằng cái output của component có khác với cái trước không
  • Để sử dụng chức năng snapshot của jest của Chúng ta cài thêm react-test-renderer
yarn add react-test-renderer --dev
  • Thêm import vào đầu file
import renderer from 'react-test-renderer';
  • Và thêm 1 test dưới:
it('matches the snapshot', () => {
      const tree = renderer.create(<App />).toJSON();
      expect(tree).toMatchSnapshot();
    });
  • Sau khi chạy xong lần đầu thì sẽ tạo ra 1 folder snapshots trong srcnhư thế này
  • Nếu lần sau chạy thành công thì nó sẽ hiển thị.
  • Nếu chúng ta có sửa 1 dòng code thì không thành công nó sẽ báo lỗi.
  • Nhưng nếu mình chủ động sửa và muốn update thì hãy ấn u để update lại snapshot.

3. Kết luận

  • Mình đã áp dụng Jest vs Enzyme vào dự án thì khá là dễ sử dụng.
  • Nhưng mình phát hiện ra rằng điều khó ở đây không phải viết unit-test như nào mà là lên kịch bản viết unit-test như nào để có thể phủ rộng nhất có thể. Thì mình hi vọng rằng bản thân trong thời gian sau có đủ hiểu biết để viết 1 blog nói về vấn đề lên kịch bản này.

4. Nguồn tham khảo