Viết Rspec cho Controller
Bài đăng này đã không được cập nhật trong 6 năm
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 trongrails_helper.rb
hoặcrspec_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).
All rights reserved