+19

React tìm thì Enzyme trốn, React trốn Enzyme vẫn tìm

■ Intro.

Đợt này mình làm việc tại nhà, ở quê giờ cũng đang vụ mùa. Sau một tối chạy mưa té khói, trời nhỏ cho vài giọt như bình xịt hoa thì xong đâu lại vào đấy. Có khi nay còn nóng "rực rỡ" hơn 😂😂

Lại nói đến mưa mùa Hạ, chẳng mong thì nó ào ào như vũ bão. Mà đợi hoài thì mất tăm mất tích luôn, nói như Đen Vâu bây giờ thì:

- Anh đi tìm thì mưa lại trốn, anh đi trốn thì mưa chẳng tìm...

Quay trở lại với câu chuyện lập trình của chúng ta, coding & unit testing cũng như cặp kẻ trốn - người tìm trong mỗi giai đoạn phát triển phần mềm vậy.

Đó giờ các vấn đề xoay quanh coding chiếm spotlight nhiều rồi, hôm nay cùng mình tìm hiểu về chủ đề unit testing - cụ thể là Áp dụng Enzyme để thực hiện unit test trong dự án ReactJS nhé 😄😄))

■ Target

Bài viết này gồm 02 phần chính:

  • Giới thiệu về Enzyme.
  • Áp dụng Enzyme trong dự án ReactJS.

Do vậy, những bạn đã có kiến thức cơ bản về React và đang muốn tìm hiểu về unit test trong ReactJS components là một trong những đối tượng chính của bài viết nhaa ^^

Bây giờ thì hãy bắt đầu thôiii !

■ Enzyme

Theo Trang chủ:

Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components' output.

Enzyme - thư viện được phát triển bởi nhà Airbnb - thông qua đa dạng các hàm từ render các components cho tới tìm kiếm hay tương tác sự kiện với các elements đã giúp cho việc thực hiện unit test trở nên dễ dàng hơn.

- Ngại gì vết bẩn vì đã có Enzyme <3

Trong Coding, khi phát triển một nghiệp vụ nào đó, chúng ta thường xác định 03 câu hỏi:

  • Input?
  • Output?
  • How to implement?.

Với Unit testing cũng như vậy. "Một kịch bản" sẽ được mô tả thông qua:

describe("NAME_1", () => {
    test("CASE_1", () => { /* ... */ });
    test("CASE_2", () => { /* ... */ });
    // ...
});

describe("NAME_2", () => { /* ... */ });
// ...

Để làm rõ điều này, cùng bắt tay vào ví dụ thực hành dưới đây ^^

■ Practices

■ Init

Khởi tạo một dự án ReactJS thông qua Create React App:

npx create-react-app PROJECT_NAME

Ta được cấu trúc thư mục như sau:

■ PROJECT_NAME
└──────────── ■ src
│            ├──────────────── 📋 App.js
│            ├──────────────── 📋 App.test.js
│            ├──────────────── 📋 setupTests.js
│            ├──────────────── ...
├──────────── 📋 package.json
│...

Tiếp theo, tiến hành cài đặt các devDependencies cần thiết:

yarn add --dev enzyme enzyme-adapter-react-16

Để chạy lệnh test, chúng ta sử dụng câu lệnh:

yarn test

Tahdaahh!!

Kết quả tương tự như vầyy: PASS

■ Coding - Config

Trên cơ sở tập trung giới thiệu về Enzyme (bỏ qua câu chuyện "code first - test last" hay "code last - test first"), chúng ta xét một ví dụ về Counter đơn giản:

function App() {
    const [counter, setcounter] = useState(0);
    
    return (
        <div>
            <h1>This is counter app</h1>
            <div id="counter-value">{counter}</div>
            <button id="increment-btn" onClick={() => setcounter(counter + 1)}>
                Increment
            </button>
            <button id="decrement-btn" onClick={() => setcounter(counter - 1)}>
                 Decrement
            </button>
        </div>
    );
}

Trước khi vào phần tiến hành viết test cho component này, hãy thêm config:

import "@testing-library/jest-dom/extend-expect";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

configure({
    adapter: new Adapter(
});

Được rồi, bắt đầu test nhé ^^

■ UI Testing

Chúng ta tiến hành test mặt UI(User Interface) trước như kiểm tra text value, status trên User Interface, etc.

Check text element

import { configure, shallow } from "enzyme";

describe("Counter Testing", () => {
    test("render the title of counter", () => { 
        const wrapper = shallow(<App />);
        expect(wrapper.find("h1").text()).toContain("This is counter app");
    });
});

Lưu ý một chút,shallow() là một hàm trong Enzyme - nhận params là một ReactElement và trả về là một wrapper instance around-rendered-output.

Hàm .find() là một trong số medthod của instance này:

wrapper.find("h1").text()

Tương tự, chúng ta tiến hành test button:

describe("Counter Testing", () => {
    // ...
    test("render a button with text of `increment`", () => { 
        const wrapper = shallow(<App />);
        expect(wrapper.find("#increment-btn").text()).toBe("Increment");
    });
    // test("render a button with text of `increment`", ...)
});

■ Logic Testing

Giả sử chưa kể các specs phát sinh, chúng ta có 03 cases cơ bản:

  • Case 1: Giá trị ban đầu của count là 0.
  • Case 2: Khi nhấn vào Increace thì giá trị count tăng lên 1 đơn vị.
  • Case 3: Khi nhấn vào Decreace thì giá trị count giảm xuống 1 đơn vị.

Case 1:

describe("Counter Testing", () => {
    // ...
    test("render the initial value of state in a div", () => {
        const wrapper = shallow(<App />);
        expect(wrapper.find("#counter-value").text()).toBe("0");
    });
});

Case 2:

describe("Counter Testing", () => {
    // ...
    test("render the click event of increace button and decrement counter value", () => {
        const wrapper = shallow(<App />);
        wrapper.find("#increment-btn").simulate("click");
        expect(wrapper.find("#counter-value").text()).toBe("1");
    });
});

Case 3:

describe("Counter Testing", () => {
    // ...
    test("render the click event of decrement button and decrement counter value", () => {
        const wrapper = shallow(<App />);
        wrapper.find("#increment-btn").simulate("click");
        expect(wrapper.find("#counter-value").text()).toBe("1");
        
        wrapper.find("#decrement-btn").simulate("click");
        expect(wrapper.find("#counter-value").text()).toBe("0");
    });
});

Như đã được mô tả trong ví dụ, find(), .text(), .simulate(),... là một số hàm được sử dụng phổ biến trong Enzyme. Ngoài ra, bạn có thể tham khảo thêm tại đây.


Challenge

Dựa vào đa dạng các utilities kể trên, mình có một challenge dành cho các bạn:

Specs thay đổi:
- Giá trị nhỏ nhất `count` có thể nhận là `0`.
- Khi `count = 0` thì `Decreace` sẽ bị `disabled`.

Hãy tham khảo phía trên rồi tự mình xử lý logic & hoàn thành đoạn unit test này nhaaa ^^

■ Refactor

beforeEach() || afterEach()

Trong mỗi test(CASE_i), dòng:

const wrapper = shallow(<App />);

luôn bị lặp đi lặp lại 😭😭

Để xử lý vấn đề này, bên Enzyme có hàm beforeEach() với cách dùng rất đơn giản:

describe("Counter Testing", () => {
    let wrapper;
    beforeEach(() => {
        wrapper = shallow(<App />);
    });
    
    // test(CASE_i) deleted the line `const wrapper = shallow(<App />);`
});

Tương tự với afterEach():

describe("Counter Testing", () => {
    // beforeEach
    
    afterEach(() => {
        wrapper.unmount();
    });
  
    // test(CASE_i)
});


shallow() vs. mount()

Một ứng dụng thì có thể có nhiều modules kèm vô số các components. Do đó, trong ví dụ trên, chúng ta tách Counter component khỏi App:

const App = () => (
    <div className="App">
        <Counter />
    </div>
);

const Counter = () => { /* title, count và 2 button về counter như trên */ };

Đến đây thì thấy bị báo FAIL test. Vào ngay App.test.js đổi .shadow() thành .mount() thì báo PASS test như cũ.


Như vậy, shallow() liệu có khác so với mount()?

Có thể nhận ra ngay điểm khác biệt giữa hai hàm này khi log luôn nè:

shallowWrapper = shallow(<App />);
console.log(shallowWrapper.debug());
// RESPONSE: shallowWrapper
<div className="App">
  <Counter />
<div>

Còn với mount() thì:

mountWrapper = mount(<App />);
console.log(mountWrapper.debug());
// RESPONSE: mountWrapper
<div className="App">
  <Counter>
    <div>
      <h1>This is counter app</h1>
      <div id="counter-value">{counter}</div>
      <button id="increment-btn">Increment</button>
      <button id="decrement-btn">Decrement</button>
    <div>
  <Counter>
<div>

Ngoài mount(), shadow(), chúng ta còn 1 hàm nữa là render(). Một bảng mô tả sự khác biệt giữa chúng:

Utils mount() render() shadow()
Render Full DOM static HTMLs chỉ "render" ra một component đang test mà không bao gồm các component con, tạo sự tách biệt việc test trên từng component-độc-lập
Common use Integration Test Integration Test Unit Test

Trên đây là một số thông tin cơ bản về Enzyme cũng như cách sử dụng. Để tìm hiểu & trải nghiệm thêm những Unit Test Challenges phức tạp & thú vị, bạn có thể đọc thêm tại đây nhé!

■ Sumup

Yayyy, vậy là chúng ta đã cùng nhau tìm hiểu về Enzyme và thử viết một chút unit test trong React component rồi nè 🎉🎉

Bên cạnh một số thư việntesting phổ biến khác như Jest, Enzyme với sức mạnh của mình cũng có thể xem như một trong những mảnh ghép quan trọng trong hệ sinh thái React.

Mình cảm ơn các bạn vì đã đọc bài viết này và hy vọng rằng nó có thể mang lại được giá trị nào đó.

Tặng mình 1 upvote để có thêm động lực cho những bài viết sắp tới nha ❤️


Và trong thời điểm hiện tại thì...

Hãy cùng nhau thực hiện quy tắc 5K được Bộ Y tế khuyến cáo:

#Coronavirus #5K #BoY Te
Khẩu trang - Khử khuẩn - Khoảng cách - Không tập trung - Khai báo y tế

để có thể giữ an toàn cho bản thân và mọi người xung quanh nhé 😺😺

Chúc các bạn cuối tuần vui vẻ ^^

■ Credits


Happy coding !


All Rights Reserved

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