Sử dụng Rspec viết unit test cho Controller trong ứng dụng Rails
Bài đăng này đã không được cập nhật trong 7 năm
Những điều cơ bản để test trong Controller
Controller spec được tách nhỏ ra bởi phương thức controller, mỗi test case được dựa trên một action và có thể gửi kèm params hoặc không. Ví dụ như sau:
it "redirects to the home page upon save" do
post :create, contact: Factory.attributes_for(:contact)
expect(response).to redirect_to root_url
end
Dưới đây là những điều cần chú ý:
- Mô tả của mỗi test case cần phải được viết một cách rỏ ràng và dễ đọc.
- Mỗi test case chỉ có 1 expect.
- Factory sẽ có nhiệm vụ tạo ra data test để truyền vào controller.
Mô tả test case
Chúng ta sẽ sử dụng ứng dụng Address Book để làm ví dụ, dưới đây là những thứ mà chúng ta cần phải test:
# spec/controllers/contacts_controller_spec.rb
require 'spec_helper'
describe ContactsController do
describe "GET #index" do
it "populates an array of contacts"
it "renders the :index view"
end
describe "GET #show" do
it "assigns the requested contact to @contact"
it "renders the :show template"
end
describe "GET #new" do
it "assigns a new Contact to @contact"
it "renders the :new template"
end
describe "POST #create" do
context "with valid attributes" do
it "saves the new contact in the database"
it "redirects to the home page"
end
context "with invalid attributes" do
it "does not save the new contact in the database"
it "re-renders the :new template"
end
end
end
Trong model spec ở trên, chúng ta đã sử dụng block describe
và context
để mô tả test case một cách dễ hiểu hơn, tất cả đều dựa vào nội dung của action trong controller. Trong trường hợp này, trường hợp test đúng là một user sẽ truyền attributes hợp lệ vào controller, trường hợp test sai là truyền attributes không hợp lệ. Nếu ứng dụng của bạn có thực hiện việc xác thực thì bạn có thể thêm nó vào trong context, test với trường hợp có login và không login, hoặc test phân quyền trong ứng dụng.
Tạo data test
Cũng như spec trong model, spec trong controller cũng cần data. Chúng ta sẽ sử dụng Factory để tạo dữ liệu mẫu.
# spec/factories/contacts.rb
factory :contact do |f|
f.firstname { Faker::Name.first_name }
f.lastname { Faker::Name.last_name }
end
Bây giờ chúng ta sẽ thêm một contact không hợp lệ nữa:
factory :invalid_contact, parent: :contact do |f|
f.firstname nil
end
Chú ý sự khác biệt là :invalid_contact
sử dụng :contact
làm đối tượng cha (Trong trường hợp này, firstname
là của chính nó, tất cả các attributes còn lại sẽ lấy từ :contact
)
Testings GET methods
Những action trong controller có phương thức GET là #index
, #new
, #show
và #edit
. Dựa vào nội dung những cái chúng ta đã nêu ra ở trên thì chúng ta thêm vào những test case sau:
# spec/controllers/contacts_controller_spec.rb
describe "GET #index" do
it "populates an array of contacts" do
contact = Factory(:contact)
get :index
expect(assigns(:contacts)).to eq([contact])
end
it "renders the :index view" do
get :index
expect(response).to render_template :index
end
end
describe "GET #show" do
it "assigns the requested contact to @contact" do
contact = Factory(:contact)
get :show, id: contact
expect(assigns(:contact)).to eq(contact)
end
it "renders the #show view" do
get :show, id: Factory(:contact)
expect(response).to render_template :show
end
end
Chúng ta hoàn toàn dựa trên code để viết được test case.
Testing POST methods
Chúng ta hãy chuyển sang phương thức #create
của controller. Khi mà user truyền các attributes cho một contact hợp lệ và không hợp lệ, ta sẽ viết các test case tương ứng:
# spec/controllers/contacts_controller_spec.rb
describe "POST create" do
context "with valid attributes" do
it "creates a new contact" do
expect{
post :create, contact: Factory.attributes_for(:contact)
}.to change(Contact,:count).by(1)
end
it "redirects to the new contact" do
post :create, contact: Factory.attributes_for(:contact)
expect(response).to redirect_to Contact.last
end
end
context "with invalid attributes" do
it "does not save the new contact" do
expect{
post :create, contact: Factory.attributes_for(:invalid_contact)
}.to_not change(Contact,:count)
end
it "re-renders the new method" do
post :create, contact: Factory.attributes_for(:invalid_contact)
expect(rsponse).to render_template :new
end
end
end
Testing PUT methods
Ở trong action #update
, chúng ta sẽ kiểm tra hai trường hợp, attributes được truyền vào trong method sẽ được assign đến model mà chúng ta update, thứ 2 là redirect path. Sau đó chúng ta sẽ kiểm tra những trường hợp trên có đúng không nếu truyền vào params không hợp lệ.
# spec/controllers/contacts_controller_spec.rb
describe 'PUT update' do
before :each do
@contact = Factory(:contact, firstname: "Lawrence", lastname: "Smith")
end
context "valid attributes" do
it "located the requested @contact" do
put :update, id: @contact, contact: Factory.attributes_for(:contact)
expect(assigns(:contact)).to eq(@contact)
end
it "changes @contact's attributes" do
put :update, id: @contact,
contact: Factory.attributes_for(:contact, firstname: "Larry", lastname: "Smith")
@contact.reload
@contact.firstname.should eq("Larry")
@contact.lastname.should eq("Smith")
end
it "redirects to the updated contact" do
put :update, id: @contact, contact: Factory.attributes_for(:contact)
expect(response).to redirect_to @contact
end
end
context "invalid attributes" do
it "locates the requested @contact" do
put :update, id: @contact, contact: Factory.attributes_for(:invalid_contact)
expect(assigns(:contact)).to eq(@contact)
end
it "does not change @contact's attributes" do
put :update, id: @contact,
contact: Factory.attributes_for(:contact, firstname: "Larry", lastname: nil)
@contact.reload
@contact.firstname.should_not eq("Larry")
@contact.lastname.should eq("Smith")
end
it "re-renders the edit method" do
put :update, id: @contact, contact: Factory.attributes_for(:invalid_contact)
expect(response).to render_template :edit
end
end
end
Những test case trên đã verify được các trường hợp đã nói ban đầu, nó đã thực sự được update, Chú ý là chúng ta đã gọi hàm reload
trong @contact
để kiểm tra đã update thành công.
Testing DELETE methods
Test cho action #destroy
là tương đối đơn giản:
# spec/controllers/contacts_controller_spec.rb
describe 'DELETE destroy' do
before :each do
@contact = Factory(:contact)
end
it "deletes the contact" do
expect{
delete :destroy, id: @contact
}.to change(Contact,:count).by(-1)
end
it "redirects to contacts#index" do
delete :destroy, id: @contact
expect(response).to redirect_to contacts_url
end
end
Test case đầu tiên là kiểm tra xem object đã thực sự được xóa chưa, test case thứ 2 là xác nhận user đã redirect ngược lại trang index.
Tổng kết
Thông qua việc test cho controller, có lẽ giờ bạn đã khá thông thạo trong việc test cho controller, nhưng mà bạn nên làm nhiều ví dụ hơn, tìm hiểu nhiều kỷ thuật hơn trong việc test với Rspec, Factory Girl và những helper khác để cho test case của bạn trở nên chuyên nghiệp hơn. Cảm ơn bạn đã đọc.
All rights reserved