[RubyonRails][Rspec] Unit Test trong rails (Phần 2)
Trong phần trước mình đã giới thiệu sương sương về Rspec trong Rails
và một số gem
hỗ trợ, và trong bài viết này mình sẽ nói nhiều hơn về những thành phần cơ bản khi viết Rspec
Sử dụng describe
, context
, it
Trong RSpec, describe, context, và it là các phương thức được sử dụng để viết và tổ chức các test case một cách rõ ràng và dễ đọc.
-
describe:
describe
được sử dụng để nhóm các test case lại với nhau dưới một tiêu đề chung, thường là để mô tả một đối tượng, một class, hoặc một phương thức đang được kiểm tra. Nó giúp tổ chức test suite của bạn thành các phần nhỏ, dễ quản lý và hiểu.describe "Tên đối tượng hoặc phần của mã nguồn cần kiểm tra" do # Các test case liên quan đến đối tượng hoặc phần mã nguồn này sẽ được định nghĩa ở đây end
-
context:
context
được sử dụng để tạo ra một bối cảnh hoặc điều kiện cụ thể cho các test case. Thường được sử dụng để mô tả các trạng thái hoặc điều kiện khác nhau mà đối tượng cần được kiểm tra.context "Khi điều kiện hoặc trạng thái nhất định xảy ra" do # Các test case liên quan đến điều kiện hoặc trạng thái này sẽ được định nghĩa ở đây end
-
it:
it
được sử dụng để mô tả một test case cụ thể, trong đó bạn định nghĩa điều kiện cần kiểm tra và kết quả mong đợi của test case đó.it "Mô tả điều kiện cần kiểm tra" do # Mã kiểm tra và kết quả mong đợi sẽ được định nghĩa ở đây end
Và thứ tự của 3 thằng này theo chuẩn của nó là describe
-> context
-> it
describe 'User entity testing ' do
context 'when creating a new user' do
it 'is valid with valid attributes' do
# Kiểm tra tính hợp lệ của user khi tạo mới
end
it 'is not valid without a name' do
# Kiểm tra user không hợp lệ nếu thiếu thuộc tính name
end
end
context 'when updating an existing user' do
it 'updates the user attributes' do
# Kiểm tra cập nhật thuộc tính của user thành công
end
it 'does not allow invalid attributes' do
# Kiểm tra user không được cập nhật nếu dữ liệu mới không hợp lệ
end
end
end
let
, let!
, before
và subject
Trong quá trình viết RSpec có một số phương thức thường sử dụng để thiết lập trạng thái ban đầu và các biến trong các test case và làm cho code Rspec của bạn trở nên dễ hiểu hơn!
-
let:
let
được sử dụng để định nghĩa một biến instance trong toàn bộ phạm vi của một describe hoặc một context. Biến được tạo bởi let chỉ được khởi tạo khi nó được gọi đến lần đầu tiên trong mỗi test case.describe User do let(:user) { User.new(name: 'John', email: 'john@example.com') } it 'has a name' do expect(user.name).to eq('John') end it 'has an email' do expect(user.email).to eq('john@example.com') end end
-
let!:
let!
tương tự nhưlet
, nhưng nó sẽ tạo và khởi tạo biến ngay khi một describe hoặc một context được gọi. Điều này có nghĩa là biến được tạo bởi let! sẽ được khởi tạo trước mỗi test case.describe User do let!(:user) { User.create(name: 'John', email: 'john@example.com') } it 'has a name' do expect(user.name).to eq('John') end it 'has an email' do expect(user.email).to eq('john@example.com') end end
-
before:
before
được sử dụng để thiết lập trạng thái ban đầu hoặc thực hiện các hành động trước khi mỗi test case chạy. Nó thường được sử dụng để thực hiện các thao tác chuẩn bị dữ liệu trước khi test case được thực hiện.describe User do before do @user = User.new(name: 'John', email: 'john@example.com') end it 'has a name' do expect(@user.name).to eq('John') end it 'has an email' do expect(@user.email).to eq('john@example.com') end end
-
subject:
subject
được sử dụng để định nghĩa một biến instance mặc định mà được sử dụng trong các test case. Khi không có subject được gọi, RSpec sẽ sử dụng biến mặc định của subject là biến subject.subject
trong RSpec là một cơ chế mạnh mẽ cho phép bạn định nghĩa một biến hoặc một đối tượng mặc định để sử dụng trong các test case của mộtdescribe
hoặc mộtcontext
. subject giúp làm cho code test của bạn trở nên ngắn gọn và dễ đọc hơn bằng cách loại bỏ sự lặp lại và làm tăng tính rõ ràng của mã.-
Định nghĩa subject cơ bản
describe User do subject { User.new(name: 'John', email: 'john@example.com') } it 'has a name' do expect(subject.name).to eq('John') end it 'has an email' do expect(subject.email).to eq('john@example.com') end end
Trong ví dụ này, subject được định nghĩa là một đối tượng User mới với các thuộc tính được chỉ định. Trong mỗi test case, chúng ta sử dụng subject để tham chiếu đến đối tượng này.
-
Sử dụng subject với biến mặc định
Nếu không có
subject
được định nghĩa trong mộtdescribe
, RSpec sẽ tự động tạo ra mộtsubject
mặc định từ tên của describe.describe User do it 'has a name' do expect(subject.name).to eq('John') end it 'has an email' do expect(subject.email).to eq('john@example.com') end end
Trong trường hợp này, subject sẽ trở thành một đối tượng User mới tự động được tạo ra bởi RSpec.
-
Sử dụng subject với biến đặc biệt
Bạn có thể đặt tên cho
subject
và sử dụng nó trong toàn bộdescribe
hoặccontext
.describe User do subject(:custom_user) { User.new(name: 'Jane', email: 'jane@example.com') } it 'has a name' do expect(custom_user.name).to eq('Jane') end it 'has an email' do expect(custom_user.email).to eq('jane@example.com') end end
Trong trường hợp này,
custom_user
được định nghĩa là một biếnsubject
với tên làcustom_user
, và bạn có thể sử dụng nó trong các test case trongdescribe
.Một ví dụ khác khi dùng
subject
. Giả sử bạn đang viết Rspec cho controller và đang viết testcase cho phương thứcCreate
với method (Post) thì bạn hoàn toàn có thể viết như sau:describe 'POST #create' do subject(:action) { post user_path, params: params } context 'mô tả context 1' do # thay đổi params linh hoạt để phù hợp với testcase let(:params) { { user_atrributes: valid_params } } it 'Mô tả điều kiện cần kiểm tra' do action expect(response).to ... end end context 'mô tả context 2' do # thay đổi params linh hoạt để phù hợp với testcase let(:params) { { user_atrributes: valid_params } } it 'Mô tả điều kiện cần kiểm tra' do expect ... end it 'Mô tả điều kiện cần kiểm tra' do action expect(response).to ... end end context 'mô tả context 3' do # thay đổi params linh hoạt để phù hợp với testcase let(:params) { { user_atrributes: invalid_params } } it 'Mô tả điều kiện cần kiểm tra' do expect ... end it 'Mô tả điều kiện cần kiểm tra' do action expect(response).to ... end end end
-
Mocking và Stubbing
MOCK
vàSTUBS
là hai kỹ thuật quan trọng trong unit testing để giả lập hoặc thay thế các thành phần phụ thuộc (dependencies) của một phần của mã nguồn mà bạn đang kiểm tra. Điều này giúp bạn kiểm tra chức năng của một phần mã nguồn mà không phụ thuộc vào hành vi hoặc trạng thái của các thành phần khác
Giả sử bạn có 1 class Calculator
và có phương thức add
class Calculator
def add(a, b)
return a + b
end
end
-
Stubbing
allow(some_object).to receive(some_method).and_return(some_value)
Trong stubbing, chúng ta giả lập hoặc thay thế một phần của mã nguồn để kiểm tra hành vi của mã nguồn khác. Trong ví dụ này, chúng ta sẽ sử dụng stubbing để thay thế phương thức
add
của classCalculator
.require 'calculator' RSpec.describe Calculator do describe '#add' do it 'adds two numbers' do calculator = Calculator.new allow(calculator).to receive(:add).and_return(5) result = calculator.add(2, 3) expect(result).to eq(5) end end end
Ở đây, chúng ta sử dụng phương thức
allow
của RSpec để mock phương thứcadd
của đối tượngcalculator
và trả về giá trị5
. Điều này giả lập việc thực thi của phương thứcadd
mà không cần dùng tớiimplement
thực tế của nó. -
Mocking
`expect(some_object).to receive(some_method).and_return(some_value)`
Về cơ bản thì Mocking và Stubbing khá giống nhau chỉ khác nhau ở chỗ:
Mock
thể hiện những sự mong đợi khi gọi đến một method nào đó, nó có thể là kết quả trả về, là đối số truyền vào method đó hay là số lần gọi đến method đó.- Trong khi đó
Stub
chỉ như là định nghĩa/cho phép một object gọi đến một method và trả về một kết quả nào đó
Ngoài
allow
vàexpect
chúng ta còn có các phương thức hay dùng, các bạn tham khảo tại đây
Sử dụng Factory Bot hiệu quả
-
Cơ bản về Factory Bot
Factory Bot là một thư viện giúp tạo ra dữ liệu mẫu cho các test case một cách linh hoạt và dễ dàng. Bằng cách sử dụng Factory Bot, bạn có thể tạo ra các đối tượng và dữ liệu liên quan một cách tự động, giúp rút ngắn thời gian viết test case và tạo ra các trường hợp test phong phú.
-
Nâng cao với Factory Bot
-
Sử dụng Traits
Traits
là một tính năng mạnh mẽ của Factory Bot cho phép bạn định nghĩa các thuộc tính và hành vi đặc biệt cho các factory. Điều này giúp tạo ra các phiên bản của cùng một đối tượng với các thuộc tính khác nhau dễ dàng hơn. -
Kết hợp với Faker
Faker là một thư viện giúp tạo ra dữ liệu giả mạo một cách tự động. Kết hợp Factory Bot với Faker giúp bạn tạo ra các dữ liệu thực tế và phong phú hơn cho các test case của mình.
-
-
Ví dụ về Sử dụng Factory Bot
Giả sử bạn có một model User trong ứng dụng của mình và bạn muốn viết test case cho nó bằng cách sử dụng Factory Bot. Dưới đây là một ví dụ về cách sử dụng Factory Bot để tạo ra các đối tượng User trong test case:
# spec/factories/users.rb FactoryBot.define do factory :user do name { Faker::Name.name } email { Faker::Internet.email } password { Faker::Internet.password } trait :admin do admin { true } end end end
Trong ví dụ trên, chúng ta định nghĩa một
factory
cho đối tượngUser
với các thuộc tínhname, email và password
được tạo ra bằng cách sử dụng Faker. Ngoài ra, chúng ta cũng định nghĩa một trait là:admin
để tạo ra các user có quyền admin.Sau đó, bạn có thể sử dụng factory này trong test case của mình như sau:
# spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do it 'is valid with valid attributes' do user = FactoryBot.create(:user) expect(user).to be_valid end it 'is an admin' do admin_user = FactoryBot.create(:user, :admin) expect(admin_user.admin).to eq(true) end end
Kết luận
Trên đây là những hiểu biết của mình về cách thực hiện unit testing trong Rails sử dụng RSpec. Hi vọng những chia sẻ ở trên sẽ giúp ích được cho bạn đọc khi bắt đầu dự án Ruby on Rails của mình.
Tài liệu tham khảo
All rights reserved