Khi phát triển một ứng dụng React, việc đảm bảo tính ổn định và chất lượng của sản phẩm là rất quan trọng. Một ứng dụng không ổn định và kém chất lượng có thể gây ra những vấn đề nghiêm trọng cho người dùng, làm giảm trải nghiệm và ảnh hưởng đến uy tín thương hiệu. Do đó, việc thực hiện Testing trong quá trình phát triển React là cần thiết để đảm bảo sản phẩm đáng tin cậy.
Trong bài viết này, chúng ta sẽ tập trung vào Testing trong React và các phương pháp đảm bảo tính đáng tin cậy và chất lượng của ứng dụng. Chúng ta sẽ khám phá các loại Testing khác nhau và cách áp dụng chúng vào phát triển React.
1. Testing trong React JS là gì?
Testing trong React là quá trình kiểm tra tính chính xác và chức năng của các thành phần trong ứng dụng React. Quá trình này giúp phát hiện sớm các lỗi, giảm thiểu chi phí và thời gian phát triển, và đảm bảo rằng các thay đổi không gây ra vấn đề mới. Các kỹ thuật kiểm tra phổ biến trong React bao gồm Unit Testing, Integration Testing, Snapshot Testing và End-to-End Testing.
2. Các components quan trọng trong Testing
Hàm describe() và it()
describe()
và it()
là hai hàm quan trọng trong Jest, một trong những công cụ phổ biến nhất để kiểm thử ứng dụng React. Cả hai hàm này được sử dụng để định nghĩa các bài kiểm thử và tạo ra các test suites. Dưới đây là một số thông tin chi tiết về describe()
và it()
:
Bài viết này được đăng tại [free tuts .net]
Hàm describe()
describe()
được sử dụng để định nghĩa một test suite trong Jest. Test suite là một nhóm các bài kiểm thử liên quan đến nhau. Hàm describe()
nhận vào hai tham số:
1.Một chuỗi mô tả về test suite.
2. Một hàm callback
chứa các bài kiểm thử.
Ví Dụ:
1
2
3
4
5
|
describe( 'Example test suite' , () => { it( 'should do something' , () => { // Your test code goes here }); }); |
Hàm it()
it()
được sử dụng để định nghĩa một bài kiểm thử trong Jest. Mỗi bài kiểm thử đều phải nằm trong một test suite được định nghĩa bởi hàm describe()
. Hàm it()
nhận vào hai tham số:
1. Một chuỗi mô tả về bài kiểm thử.
2. Một hàm callback chứa mã kiểm thử.
Ví dụ:
1
2
3
4
5
|
describe( 'Example test suite' , () => { it( 'should do something' , () => { // Your test code goes here }); }); |
Sử dụng đúng cách describe()
và it()
giúp cho việc tổ chức và quản lý các bài kiểm thử trở nên dễ dàng và hiệu quả hơn.
Hàm expect()
expect()
là một hàm quan trọng trong Jest, một trong những công cụ phổ biến nhất để kiểm thử ứng dụng React. Hàm này được sử dụng để kiểm tra các giá trị và xác định liệu chúng có đúng như mong đợi hay không. Dưới đây là một số thông tin chi tiết về expect()
:
Sử dụng expect()
Hàm expect()
nhận vào một giá trị cần kiểm tra và trả về một đối tượng có các phương thức kiểm tra khác nhau. Các phương thức này bao gồm:toBe(value)
: kiểm tra xem giá trị có bằng value hay không (dùng cho kiểu dữ liệu nguyên thuỷ như number, string, boolean, null, undefined).toEqual(value)
: kiểm tra xem giá trị có bằng value hay không (dùng cho kiểu dữ liệu phức tạp như object, array).not.toBe(value)
: kiểm tra xem giá trị có khác với value hay không.toBeTruthy()
: kiểm tra xem giá trị có đúng là truthy hay không.toBeFalsy()
: kiểm tra xem giá trị có đúng là falsy hay không.- Và nhiều phương thức khác.
Ví dụ:
1
2
3
|
test( 'adds 1 + 2 to equal 3' , () => { expect(1 + 2).toBe(3); }); |
So sánh với toBe() và toEqual()
Khi so sánh giá trị, bạn có thể sử dụng toBe()
hoặc toEqual()
. Tuy nhiên, có một điều cần lưu ý rằng:
toBe()
so sánh địa chỉ vùng nhớ của các giá trị. Do đó, nó chỉ hoạt động cho các giá trị nguyên thuỷ (number, string, boolean, null, undefined).toEqual()
so sánh giá trị thực sự của các giá trị. Do đó, nó hoạt động cho cả giá trị nguyên thuỷ và phức tạp (object, array).
Ví dụ:
1
2
3
4
5
|
test( 'object assignment' , () => { const data = { one: 1 }; data[ 'two' ] = 2; expect(data).toEqual({ one: 1, two: 2 }); }); |
Trong ví dụ trên, nếu bạn sử dụng toBe()
thay vì toEqual()
, bài kiểm thử sẽ không được chấp nhận, bởi vì toBe()
không so sánh được các giá trị phức tạp như object.
Hàm render()
render()
là một trong những phương thức quan trọng nhất trong Jest và React Testing Library, giúp bạn render một component React và trả về một đối tượng wrapper. Dưới đây là một số thông tin chi tiết về render()
:
Sử dụng render()
Phương thức render()
được sử dụng để render một component React vào DOM ảo (virtual DOM) của Jest. Kết quả trả về là một đối tượng wrapper chứa các phương thức để tìm kiếm và tương tác với các phần tử trong component.
Ví dụ:
1
2
3
4
5
6
7
|
import { render } from '@testing-library/react' ; test( 'renders a heading' , () => { const { getByRole } = render(<MyComponent />); const heading = getByRole( 'heading' , { name: /hello/i }); expect(heading).toBeInTheDocument(); }); |
Trong ví dụ trên, render()
được sử dụng để render component <MyComponent />
vào DOM ảo. Kết quả trả về được lưu vào biến { getByRole }
, sau đó được sử dụng để tìm kiếm phần tử heading và kiểm tra xem nó có tồn tại trong DOM hay không.
Tham số của render()
Phương thức render()
nhận vào một số tham số để cấu hình cách render component, bao gồm:
- component: component React cần render.
- options: tùy chọn cấu hình, bao gồm các thuộc tính như wrapper, baseElement, queries, container, hydrate, wrapperOptions và renderOptions.
Ví dụ:
1
2
3
4
5
6
7
8
9
|
import { render } from '@testing-library/react' ; test( 'renders a component with props' , () => { const { getByRole } = render( <MyComponent name= "John" age={30} /> ); const heading = getByRole( 'heading' , { name: /hello/i }); expect(heading).toBeInTheDocument(); }); |
Trong ví dụ trên, render()
được sử dụng để render component <MyComponent />
với các props được truyền vào. Kết quả trả về được lưu vào biến { getByRole
}, sau đó được sử dụng để tìm kiếm phần tử heading và kiểm tra xem nó có tồn tại trong DOM hay không.
Tìm kiếm phần tử trong component
Khi sử dụng render()
, bạn có thể tìm kiếm các phần tử trong component bằng cách sử dụng các phương thức như getByRole()
, getByText()
, getByTestId()
… Những phương thức này sẽ trả về phần tử đầu tiên tìm được trong component và nếu không tìm thấy phần tử nào, chúng sẽ ném ra một ngoại lệ (exception).
Ví Dụ:
1
2
3
4
5
6
7
8
|
import { render } from '@testing-library/react' ; import MyButton from './MyButton' ; test( 'renders a button with text "Click me!"' , () => { const { getByRole } = render(<MyButton label= "Click me!" />); const button = getByRole( 'button' , { name: /click me/i }); expect(button).toBeInTheDocument(); }); |
Ở ví dụ này, chúng ta đang kiểm tra xem component MyButton có được render ra với nội dung là “Click me!” hay không. Để làm điều này, chúng ta truyền vào prop label với giá trị “Click me!” và sử dụng phương thức getByRole()
để tìm kiếm phần tử button trong component. Kết quả trả về được lưu vào biến button, sau đó kiểm tra xem phần tử này có tồn tại trong DOM hay không bằng cách sử dụng phương thức toBeInTheDocument()
của Jest. Nếu phần tử này không tồn tại, Jest sẽ ném ra một ngoại lệ và test sẽ fail.
screen
Trong React Testing, thư viện Testing Library đã cung cấp cho chúng ta một đối tượng có tên là screen. Đây là một đối tượng được tạo ra để cung cấp các phương thức để truy cập và tương tác với các phần tử trong DOM.
Các phương thức của screen được sử dụng để tìm kiếm các phần tử dựa trên các thuộc tính của chúng. Các phương thức này thường được sử dụng để kiểm tra xem một phần tử đã được render ra hay chưa, hoặc để lấy giá trị của một phần tử trong DOM.
Dưới đây là một ví dụ sử dụng screen để kiểm tra xem một phần tử có tồn tại trong DOM hay không:
1
2
3
4
5
6
7
8
|
import { render, screen } from '@testing-library/react' ; import MyComponent from './MyComponent' ; test( 'renders a heading with text "Hello, world!"' , () => { render(<MyComponent />); const headingElement = screen.getByRole( 'heading' , { name: /hello, world!/i }); expect(headingElement).toBeInTheDocument(); }); |
Ở ví dụ này, chúng ta sử dụng phương thức getByRole()
của screen để tìm kiếm phần tử <h1>
có nội dung là “Hello, world!”. Nếu phần tử này tồn tại trong DOM, Jest sẽ không ném ra ngoại lệ và test sẽ pass. Nếu không tồn tại, Jest
sẽ ném ra một ngoại lệ và test sẽ fail.
fireEvent là một phương thức của Testing Library được sử dụng để kích hoạt các sự kiện trên các phần tử DOM. Khi chúng ta test một component React, thì việc kiểm tra sự thay đổi của component đó sau khi các sự kiện được kích hoạt là rất quan trọng.
Dưới đây là một ví dụ sử dụng fireEvent để kích hoạt sự kiện click trên một phần tử button:
1
2
3
4
5
6
7
8
9
10
|
import { render, fireEvent, screen } from '@testing-library/react' ; import MyButton from './MyButton' ; test( 'calls the onClick function when the button is clicked' , () => { const handleClick = jest.fn(); render(<MyButton onClick={handleClick} />); const button = screen.getByRole( 'button' ); fireEvent.click(button); expect(handleClick).toHaveBeenCalledTimes(1); }); |
Ở ví dụ này, chúng ta đã tạo một mock function handleClick bằng Jest để kiểm tra xem sự kiện click đã được kích hoạt hay chưa. Sau đó, chúng ta sử dụng phương thức getByRole()
của screen để tìm kiếm phần tử button và kích hoạt sự kiện click bằng phương thức fireEvent.click()
. Cuối cùng, chúng ta kiểm tra xem function handleClick đã được gọi hay chưa bằng phương thức toHaveBeenCalledTimes()
của Jest. Nếu function này được gọi một lần, test sẽ pass. Nếu không, test sẽ fail.
Để hiểu rõ hơn về cách sử dụng fireEvent, ta có thể xem thêm ví dụ dưới đây:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import { render, fireEvent, screen } from '@testing-library/react' ; import MyForm from './MyForm' ; test( 'submits the form with the correct values' , () => { const handleSubmit = jest.fn(); render(<MyForm onSubmit={handleSubmit} />); const inputName = screen.getByLabelText( 'Name:' ); const inputEmail = screen.getByLabelText( 'Email:' ); const submitButton = screen.getByRole( 'button' , { name: 'Submit' }); fireEvent.change(inputName, { target: { value: 'John' } }); fireEvent.click(submitButton); expect(handleSubmit).toHaveBeenCalledTimes(1); expect(handleSubmit).toHaveBeenCalledWith({ name: 'John' , }); }); |
Ở ví dụ này, chúng ta đang test một form với hai trường input là “Name” và “Email”. Khi người dùng nhập thông tin vào form và click vào nút “Submit”, form sẽ submit với các giá trị tương ứng của hai trường input. Chúng ta sử dụng fireEvent để mô phỏng hành động của người dùng, giả lập việc nhập dữ liệu và click vào nút “Submit”.
Đầu tiên, chúng ta sử dụng getByLabelText của screen để tìm kiếm các input theo nhãn của chúng. Sau đó, chúng ta sử dụng fireEvent.change để giả lập việc người dùng nhập dữ liệu vào các trường input. Đối số thứ hai của phương thức fireEvent.change là một object mô tả giá trị mới của input.
Cuối cùng, chúng ta sử dụng fireEvent.click để giả lập việc click vào nút “Submit”. Sau đó, chúng ta kiểm tra xem function handleSubmit đã được gọi với đúng giá trị hay chưa bằng phương thức toHaveBeenCalledTimes()
và toHaveBeenCalledWith()
của Jest.
Như vậy, fireEvent là một công cụ mạnh mẽ giúp chúng ta kiểm tra sự thay đổi của component sau khi các sự kiện được kích hoạt. Bằng cách sử dụng fireEvent một cách đúng đắn, ta có thể test các component React của mình một cách chính xác và hiệu quả.
Hàm act()
Trong React Testing, act() là một phương thức quan trọng để đảm bảo rằng các thao tác giao diện (user interactions) và cập nhật state của component được thực hiện đồng bộ và đúng cách.
Ví dụ, khi một component React được render, React sẽ đưa component đó vào trong một hàng đợi (queue) để xử lý. Nếu ta muốn thực hiện một thao tác giao diện như click vào một nút, chúng ta cần đảm bảo rằng các hành động này được xử lý đồng bộ, tức là chúng ta phải chờ đợi component được update trước khi kiểm tra kết quả của test.
Ví dụ sau sẽ minh họa cách sử dụng act() để kiểm tra một component React đơn giản:
Ví Dụ:
1
2
3
4
5
6
7
8
9
10
|
import { render, screen, act } from '@testing-library/react' ; import MyComponent from './MyComponent' ; test( 'renders the component with the correct text' , async () => { await act(async () => { render(<MyComponent />); }); expect(screen.getByText( 'Hello, world!' )).toBeInTheDocument(); }); |
Ở ví dụ này, chúng ta sử dụng act()
để đảm bảo rằng component MyComponent đã được render hoàn toàn trước khi kiểm tra kết quả của test. Chúng ta sử dụng async/await để đợi cho act()
hoàn thành việc thực hiện các hành động.
Ngoài ra, act()
còn hỗ trợ cho việc update state của component React. Ví dụ sau minh họa cách sử dụng act()
để cập nhật state của một component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import { render, screen, act } from '@testing-library/react' ; import MyComponent from './MyComponent' ; test( 'renders the component with the updated state' , async () => { await act(async () => { render(<MyComponent />); }); const button = screen.getByRole( 'button' ); expect(button).toBeInTheDocument(); await act(async () => { button.click(); }); expect(screen.getByText( 'Clicked!' )).toBeInTheDocument(); }); |
Ở ví dụ này, chúng ta sử dụng act()
để cập nhật state của component khi người dùng click vào một nút. Chúng ta sử dụng async/await để đợi cho act()
hoàn thành việc thực hiện các hành động và kiểm tra kết quả của test.
act()
là một công cụ quan trọng giúp chúng ta kiểm tra các component React của mình một cách chính xác và đáng tin cậy. Tuy nhiên, cần lưu ý rằng việc sử dụng act()
không phải là bắt buộc cho tất cả các test. Chúng ta nên sử dụng act()
trong những trường hợp cần thi
3. Kết Luận
Testing đóng vai trò quan trọng trong quá trình phát triển ứng dụng React. Sử dụng các công cụ và kỹ thuật kiểm tra như Jest, React Testing Library, Enzyme, Snapshot Testing và Integration Testing, bạn có thể đảm bảo ứng dụng hoạt động đúng như mong đợi và đáp ứng nhu cầu người dùng. Điều này không chỉ cải thiện chất lượng và độ tin cậy của ứng dụng mà còn mang lại trải nghiệm tốt hơn cho người dùng.