Viết Rspec cho Controller
Viết Rspec là 1 phần không thể thiếu trong quá trình phát triển ứng dụng, bên cạnh những phần test logic trong Model thì phần viết test cho controller cũng là 1 phần khá quan trọng của việc viết Rspec. Tổ chức test. 'Describe' và 'Context' là 2 thành phần giúp cho phần tổ chức test của chúng ...
Viết Rspec là 1 phần không thể thiếu trong quá trình phát triển ứng dụng, bên cạnh những phần test logic trong Model thì phần viết test cho controller cũng là 1 phần khá quan trọng của việc viết Rspec.
Tổ chức test.
'Describe' và 'Context' là 2 thành phần giúp cho phần tổ chức test của chúng ta được trở nên rõ ràng và dễ đọc dựa trên các controller action và các trường hợp mà chúng ta test. Betterspecs.org cung cấp những điều cơ bản về viết test, nó sẽ giúp chúng ta viết test 1 cách đẹp mắt và tốt hơn.
The purpose of 'describe' is to wrap a set of tests against one functionality while 'context' is to wrap a set of tests against one functionality under the same state. Describe vs. Context in RSpec by Ming Liu
=> Mục đích của 'describe' là bao gồm 1 set các tests đối với 1 chức năng cụ thể.Trong khi 'context' bao gồm các tests đối với 1 chức năng trong cùng 1 trạng thái.
Chúng ta sẽ đặt mỗi HTTP session trong mỗi 'describe' khác nhau cho: stories_controller_spec.rb.
describe "Stories" do describe "GET stories#index" do context "when the user is an admin" do it "should list titles of all stories" end context "when the user is not an admin" do it "should list titles of users own stories" do end
Khi bạn muốn kiểm soát authorization access, bạn có thể tạo context mới cho mỗi trường hợp, chảng hạn context cho đăng nhập và không:
context "when the user is logged in" do it "should render stories#index" end context "when the user is logged out" do it "should redirect to the login page" end end
Mặc định, RSpec-Rails configuration disable chức năng render templates cho controller spec.Bạn có thể bật lại nó bằng các add render_views.
- Tổng thể, add vào RSpec.configure block trong rails_helper.rb hoặc rspec_helper file.
- Đối với mỗi nhóm riêng lẻ:
describe "GET stories#show" do it "should render stories#show template" do end end describe "GET stories#new" do it "should render stories#new template" do end end
Nó sẽ rất thông dụng cho việc check valid hay invalid thuộc tính trước khi save vào DB:
describe "POST stories#create" do context "with valid attributes" do it "should save the new story in the database" it "should redirect to the stories#index page" end context "with invalid attributes" do it "should not save the new story in the database" it "should render stories#new template" end end
Chuẩn bị data test.
Chúng ta sử dụng factories để tạo data cho controller specs với gem FactoryBot, ví dụ:
FactoryBot.define do factory :story do user sequence(:title) { |n| "Title#{n}" } sequence(:content) { |n| "Content#{n}" } end end
Test các action.
1.#index
def index @stories = Story.view_premissions(current_user). end
Trong model story.rb
def self.view_premissions(current_user) current_user.role.admin? ? Story.all : current_user.stories end
với những thông tin trên ta có thể tạo test GET stories#index như sau:
describe "GET stories#index" do context "when the user is an admin" do it "should list titles of all stories" do admin = create(:admin) stories = create_list(:story, 10, user: admin) login_as(admin, scope: :user) visit stories_path stories.each do |story| page.should have_content(story.title) end end end context "when the user is not an admin" do it "should list titles of users own stories" do user = create(:user) stories = create_list(:story, 10, user: user) login_as(user, scope: :user) visit stories_path stories.each do |story| page.should have_content(story.title) end end end end
Bạn có thể thấy, ta tạo 2 context khác nhau của user role(admin và không phải admin). Sử dụng create(:user) hoặc create_list(:story, 10, user: user) bạn có thể tạo nhiều story khác nhau cho 1 user.Mottj cách khác để tạo user là dùng let or before blocks.
- #show Bạn có thể tạo test cho show bằng cách tương tự, sự khác biệt thì tùy vào code của bạn cho action show:
describe "GET stories#show" do it "should render stories#show template" do user = create(:user) story = create(:story, user: user) login_as(user, scope: :user) visit story_path(story.id) page.should have_content(story.title) page.should have_content(story.content) end end
- #new và #create
# GET stories#new def new @story = Story.new end # POST stories#create def create @story = Story.new(story_params) if @story.save redirect_to story_path(@story), success: "Story is successfully created." else render action: :new, error: "Error while creating new story" end end private def story_params params.require(:story).permit(:title, :content) end
Action new render stories#new template, nó là 1 form được điền các thông tin của 1 story mới trước khi được tạo:
describe "POST stories#create" do it "should create a new story" do user = create(:user) login_as(user, scope: :user) visit new_stories_path fill_in "story_title", with: "Ruby on Rails" fill_in "story_content", with: "Text about Ruby on Rails" expect { click_button "Save" }.to change(Story, :count).by(1) end end
- #update
def update if @story.update(story_params) flash[:success] = "Story #{@story.title} is successfully updated." redirect_to story_path(@story) else flash[:error] = "Error while updating story" redirect_to story_path(@story) end end private def story_params params.require(:story).permit(:title, :content) end
Khi 1 story được tạo, nó có thể cho phép ta update, bằng cách visit edit page:
describe "PUT stories#update" do it "should update an existing story" do user = create(:user) login_as(user, scope: :user) story = create(:story) visit edit_story_path(story) fill_in "story_title", with: "React" fill_in "story_content", with: "Text about React" click_button "Save" expect(story.reload.title).to eq "React" expect(story.content).to eq "Text about React" end end
- #delete
def destroy authorize @story if @story.destroy flash[:success] = "Story #{@story.title} removed successfully" redirect_to stories_path else flash[:error] = "Error while removing story!" redirect_to story_path(@story) end end
test case cho delete:
describe "DELETE stories#destroy" do it "should delete a story" do user = create(:admin) story = create(:story, user: user) login_as(user, scope: :user) visit story_path(story.id) page.should have_link("Delete") expect { click_link "Delete" }.to change(Story, :count).by(-1) end end
ở #delete, để check story đã bị xóa, ta có thể check count Story thay đổi (-1).