Viết test cho controller, sử dụng Rspec test
Đối với những developer sử dụng ngôn ngữ ruby và frameWork RubyOnRails, chúng ta đã khá quen thuộc với công việc test cùng với gem Rspec. Chúng ta phải viết unit test cho model, các method, controller,....nói chung là tất tần tật nhưng gì chúng ta code. Trong đó, việc controller là một phần luôn ...
Đối với những developer sử dụng ngôn ngữ ruby và frameWork RubyOnRails, chúng ta đã khá quen thuộc với công việc test cùng với gem Rspec. Chúng ta phải viết unit test cho model, các method, controller,....nói chung là tất tần tật nhưng gì chúng ta code. Trong đó, việc controller là một phần luôn luôn phải được viết test, mà việc test controller không mấy dễ dàng với newbie. Bài viết này, tôi xin phép được chia sẻ sơ lược về cách viết rspec cho controller một cách hiệu quả.
Unauthenticated controller specs
Chúng ta sẽ bắt đầu với một controller của một app đơn giản. HomeController có công việc: Tải hom page cho người dùng khi họ chưa đăng nhập
app/controllers/home_controller.rb
class HomeController < ApplicationController skip_before_action :authenticate_user! def index end end
Để tạo file test cho controller này, chúng ta sẽ sử dụng công cụ generator được tạo bở rspec
bin/rails g rspec:controller home
Câu lênh sẽ tạo 1 file rspec ở trong đường dẫn: spec/controllers/home_controller_spec.rb và có nội dung khởi điểm như sau:
require 'rails_helper' RSpec.describe HomeController, type: :controller do end
Tiếp theo, chúng ta tạo một block describe cho method index bên trong khối RSpec.describe. ta sẽ thực hiện việc request đến controller và kì vọng một response successfully.
require 'rails_helper' RSpec.describe HomeController, type: :controller do describe "#index" do it "responds successfully" do get :index expect(response).to be_success end end end
response là object chứa tất cả dữ liệu mà server trả về chon browser, bao gôm cả mã responsecode. Câu lệnh be_success sẽ kiểm tra sem response status của gói tin có phải là successful (200) hoặc không (ví dụ 404, 500..) Chạy thử chút xem sao nhỉ!
rspec spec/controllers
Running via Spring preloader in process 44906 HomeController #index responds successfully Finished in 0.01775 seconds (files took 0.3492 seconds to load) 1 example, 0 failures
Việc test này có vẻ nhàm chán ? Công việc của controller thực tế là như vậy. Chúng ta phải gửi một params từ brower, xử nó với các Ruby object, rồi respond về browser. Chúng ta thử đi ngược bài test xem có gì xảy ra nào! Thay vì kì vọng be_success, chúng ta hãy kì vọng ngược lại:
require 'rails_helper' RSpec.describe HomeController, type: :controller do describe "#index" do it "responds successfully" do get :index expect(response).to_not be_success end end end
Sure là có lỗi ngay:
Failures: 1) HomeController#index responds successfully Failure/Error: expect(response).to_not be_success expected `#<ActionDispatch::TestResponse:0x007f9b16cdd350 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mu...:Headers:0x007f9b16cc7230 @req=#<ActionController::TestRequest:0x007f9b16cdd508 ...>>, @variant=[]>>.success?` to return false, got true
be_succes chỉ kiểm tra xem status trả về có successful không. Nếu ta muốn kiểm tra cụ thể là mã HTTp response là 200 hay không thì ta có thể sử dụng cú pháp như sau:
it "returns a 200 response" do get :index expect(response).to have_http_status "200" end
Vẫn có vẻ nhàm chán nhỉ :v. Tuy nhiên, vẫn có một số thứ thú vị ở đây. Hãy nhìn vào controller lần nữa, chúng ta có một dòng skip_before_action để vô hiệu hóa việc xác thực người dùng, thử comment lại dòng này và chạy test nhé!
Failures: 1) HomeController returns a 200 response Failure/Error: get :index Devise::MissingWarden: Devise could not find the `Warden::Proxy` instance on your request environment. Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack. If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object fo
Thú vị phải không =)))) BÀi tesst trả về một lỗi thông báo rằng thiếu Devise helpers trong bài test. Điều đó có nghĩa là dòng skip_before_action trong controller đang hoạt động. Chúng ta sẽ xem xét việc add thêm helper để xử lí vấn đề này sau. bây giờ hãy uncomment lại dòng code để hồi phục controller như cũ.
Authenticated controller specs
Trong phần này, chúng ta sẽ giải quyết trường hợp controller có sử dụng authenticated.
class ProjectsController < ApplicationController def index end end
Chúng ta tiếp tục create một file rspec test giống như trên với bài test status code:
rails g rspec:controller projects
1 require 'rails_helper' 2 3 RSpec.describe ProjectsController, type: :controller do 4 describe "#index" do 5 it "responds successfully" do 6 get :index 7 expect(response).to be_success 8 end 9 10 it "returns a 200 response" do 11 get :index 12 expect(response).to have_http_status "200" 13 end 14 end 15 end
Chạy rspec chúng ta có thể thấy lỗi báo thiếu Devise helpers giống ví dụ trên. Cách giải quyết là thêm dòng sau vào file spec/rails_helper.rb
1 RSpec.configure do |config| 2 # Other stuff from the config block omitted ... 3 4 # Use Devise test helpers in controller specs 5 config.include Devise::Test::ControllerHelpers, type: :controller 6 end
Chạy rspec tiếp tục vẫn lỗi?????!!!!!!, nhưng chúng ta có một lỗi khác. Ở bài test,chúng ta kì vọng status code là 200, nhưng thực tế lại trả về 302. Vấn đề ở đây là action index yêu cầu user phải đăng nhập, nhưng chúng ta không có làm điều đó trong bài test.
Bây giờ, chung ta cần tạo rá một user ảo, sau đó dùng user này để đăng nhập. Ta sẽ create user trong một khối before block, và sưr dụng method sign_in để đăng nhập:
1 require 'rails_helper' 2 3 RSpec.describe ProjectsController, type: :controller do 4 describe "#index" do 5 before do 6 @user = FactoryGirl.create(:user) 7 end 8 9 it "responds successfully" do 10 sign_in @user 11 get :index 12 expect(response).to be_success 13 end 14 15 it "returns a 200 response" do 16 sign_in @user 17 get :index 18 expect(response).to have_http_status "200" 19 end 20 end 21 end
Giờ chạy rspec toàn màu xanh rồi nhỉ ==))))))
Bản chất của việc viết test là chúng ta cần cover hết tất cả các trường hợp có thể xảy ra, nếu không, bài test là vô nghĩa. Ở đây, chúng ta đã test trường hợp người dùng đã đăng nhập. Giờ ta cần test trường hợp người dùng khách (không đăng nhập) Ta sẽ đặt phần test bên trên vào một context block và viết phần test mới vào một context khác.
1 require 'rails_helper' 2 3 RSpec.describe ProjectsController, type: :controller do 4 describe "#index" do 5 context "as an authenticated user" do 6 before do 7 @user = FactoryGirl.create(:user) 8 end 9 10 it "responds successfully" do 11 sign_in @user 12 get :index 13 expect(response).to be_success 14 end 15 16 it "returns a 200 response" do 17 sign_in @user18 get :index 19 expect(response).to have_http_status "200" 20 end 21 end 22 23 context "as a guest" do 24 # tests will go here 25 end 26 end 27 end
Giờ ta đang có 2 context nằm trong một describe cho action index. context đầu tiên test cho trường người dùng đã đăng nhập. Phần before block dùng để tạo user đã nằm ngoài context "as a guest".
1 context "as a guest" do 2 it "returns a 302 response" do 3 get :index 4 expect(response).to have_http_status "302" 5 end 6 7 it "redirects to the sign-in page" do 8 get :index 9 expect(response).to redirect_to "/users/sign_in" 10 end 11 end
Bài test trên kì vọng trong trường hợp user không đăng nhập sẽ trả về status 302 và redirect trình duyệt về trang 'user/sign_in'
Công việc tiếp theo tuơng tự chúng ta sẽ lần lượt test các method trong controller như: create, show, update, delete, edit,.....theo mindset như trên. Hi vọng mọi người đã nắm được bước đầu tiên trong việc viết test và yêu thích nó. Chúc các bạn may mắn !!