如何用 Factory Girl 创建 DRYer Rspec
How to create DRYer Rspec with Factory Girl
我正在构建一个简单的博客应用程序,以便与 RSpec 和 Factory Girl 一起学习 BDD/TDD。通过这个过程,我继续 运行 进入 'Failures' 但我相信他们更多地与我如何使用 Factory Girl 有关。
正如您将在下面看到的,为了让我的规格通过,我很难让我的测试保持干燥 - 一定是我误解了什么。你会注意到,我并没有充分利用 Factory Girl 的潜力,有时甚至完全跳过它。我发现在规范中使用 get :create
、get :show
或 put :update
等函数时,我经常 运行 遇到问题。
我目前停留在 #PUT update
规范上,该规范应该简单地测试 @post
变量的赋值。我已经尝试了在网上找到的多种类型的这种规范,但 none 似乎有效 - 因此,它是 Factory Girl 吗?也许我在网上找到的规格是过时的 Rspec 版本?
我正在使用:
Rspec 3.1.7
Rails 4.1.6
posts_controller_spec.rb
require 'rails_helper'
require 'shoulda-matchers'
RSpec.describe PostsController, :type => :controller do
describe "#GET index" do
it 'renders the index template' do
get :index
expect(response).to be_success
end
it "assigns all posts as @posts" do
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :index
expect(assigns(:posts)).to eq([post])
end
end
describe '#GET show' do
it 'assigns the request post to @post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :show, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#GET create' do
context 'with valid attributes' do
before :each do
post :create, post: attributes_for(:post)
end
it 'creates the post' do
expect(Post.count).to eq(1)
expect(flash[:notice]).to eq('Your post has been saved!')
end
it 'assigns a newly created post as @post' do
expect(assigns(:post)).to be_a(Post)
expect(assigns(:post)).to be_persisted
end
it 'redirects to the "show" action for the new post' do
expect(response).to redirect_to Post.first
end
end
context 'with invalid attributes' do
before :each do
post :create, post: attributes_for(:post, title: 'ha')
end
it 'fails to create a post' do
expect(Post.count).to_not eq(1)
expect(flash[:notice]).to eq('There was an error saving your post.')
end
it 'redirects to the "new" action' do
expect(response).to redirect_to new_post_path
end
end
end
describe '#GET edit' do
it 'assigns the request post to @post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :edit, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#PUT update' do
context 'with success' do
before :each do
post :create, post: attributes_for(:post)
end
it 'assigns the post to @post' do
put :update, id: post.id
expect(assigns(:post)).to eq(post)
end
end
end
end
posts_controller.rb
class PostsController < ApplicationController
def index
@posts = Post.all.order('created_at DESC')
end
def new
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
flash[:notice] = "Your post has been saved!"
redirect_to @post
else
flash[:notice] = "There was an error saving your post."
redirect_to new_post_path
end
end
def show
@post = Post.find(params[:id])
end
def edit
@post = Post.find(params[:id])
end
def update
@post = Post.find(params[:id])
# if @post.update(params[:post].permit(:title, :body))
# flash[:notice] = "Your post is updated!"
# redirect_to @post
# else
# flash[:notice] = "There was an error updating your post."
# render :edit
# end
end
private
def post_params
params.require(:post).permit(:title, :body)
end
end
factories/post.rb
FactoryGirl.define do
factory :post do
title 'First title ever'
body 'Forage paleo aesthetic food truck. Bespoke gastropub pork belly, tattooed readymade chambray keffiyeh Truffaut ennui trust fund you probably haven\'t heard of them tousled.'
end
end
当前失败:
Failures:
1) PostsController#PUT update with success assigns the post to @post
Failure/Error: put :update, id: post.id
ArgumentError:
wrong number of arguments (0 for 1+)
# ./spec/controllers/posts_controller_spec.rb:86:in `block (4 levels) in <top (required)>'
Finished in 0.19137 seconds (files took 1.17 seconds to load)
17 examples, 1 failure
你绝对可以利用这里的工厂。
你创建的工厂其实也可以。
而不是做:
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
这样做:post = FactoryGirl.create(:post)
如果你这样做,你可以获得更多的 DRY:
# in spec/rails_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
这将允许您在规范中执行此操作:post = create(:post)
关于你的 PUT 测试,试试这个 from a previous SO answer:
describe '#PUT update' do
let(:attr) do
{ :title => 'new title', :content => 'new content' }
end
context 'with success' do
before :each do
@post = FactoryGirl.create(:post)
end
it 'assigns the post to @post' do
put :update, :id => @post.id, :post => attr
@post.reload
expect(assigns(:post)).to eq(post)
end
end
end
编辑:
此外,如果需要,不要害怕将东西移到 before :each do
中。他们擅长保持干爽
您的规范失败的直接原因是您每次测试只能调用一次控制器,而对于更新,您调用了两次:在之前的操作中,您调用了 create... 和然后在更新测试的主要部分你调用更新...控制器规格不喜欢那样。
为了使现有规范正常工作,您需要将操作前的 post :create, post: attributes_for(:post)
行替换为仅创建 post 或(如前所述)使用工厂女孩来创建一个 post - 而不是尝试通过调用控制器来完成它。
我正在构建一个简单的博客应用程序,以便与 RSpec 和 Factory Girl 一起学习 BDD/TDD。通过这个过程,我继续 运行 进入 'Failures' 但我相信他们更多地与我如何使用 Factory Girl 有关。
正如您将在下面看到的,为了让我的规格通过,我很难让我的测试保持干燥 - 一定是我误解了什么。你会注意到,我并没有充分利用 Factory Girl 的潜力,有时甚至完全跳过它。我发现在规范中使用 get :create
、get :show
或 put :update
等函数时,我经常 运行 遇到问题。
我目前停留在 #PUT update
规范上,该规范应该简单地测试 @post
变量的赋值。我已经尝试了在网上找到的多种类型的这种规范,但 none 似乎有效 - 因此,它是 Factory Girl 吗?也许我在网上找到的规格是过时的 Rspec 版本?
我正在使用: Rspec 3.1.7 Rails 4.1.6
posts_controller_spec.rb
require 'rails_helper'
require 'shoulda-matchers'
RSpec.describe PostsController, :type => :controller do
describe "#GET index" do
it 'renders the index template' do
get :index
expect(response).to be_success
end
it "assigns all posts as @posts" do
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :index
expect(assigns(:posts)).to eq([post])
end
end
describe '#GET show' do
it 'assigns the request post to @post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :show, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#GET create' do
context 'with valid attributes' do
before :each do
post :create, post: attributes_for(:post)
end
it 'creates the post' do
expect(Post.count).to eq(1)
expect(flash[:notice]).to eq('Your post has been saved!')
end
it 'assigns a newly created post as @post' do
expect(assigns(:post)).to be_a(Post)
expect(assigns(:post)).to be_persisted
end
it 'redirects to the "show" action for the new post' do
expect(response).to redirect_to Post.first
end
end
context 'with invalid attributes' do
before :each do
post :create, post: attributes_for(:post, title: 'ha')
end
it 'fails to create a post' do
expect(Post.count).to_not eq(1)
expect(flash[:notice]).to eq('There was an error saving your post.')
end
it 'redirects to the "new" action' do
expect(response).to redirect_to new_post_path
end
end
end
describe '#GET edit' do
it 'assigns the request post to @post' do
post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
get :edit, id: post.id
expect(assigns(:post)).to eq(post)
end
end
describe '#PUT update' do
context 'with success' do
before :each do
post :create, post: attributes_for(:post)
end
it 'assigns the post to @post' do
put :update, id: post.id
expect(assigns(:post)).to eq(post)
end
end
end
end
posts_controller.rb
class PostsController < ApplicationController
def index
@posts = Post.all.order('created_at DESC')
end
def new
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
flash[:notice] = "Your post has been saved!"
redirect_to @post
else
flash[:notice] = "There was an error saving your post."
redirect_to new_post_path
end
end
def show
@post = Post.find(params[:id])
end
def edit
@post = Post.find(params[:id])
end
def update
@post = Post.find(params[:id])
# if @post.update(params[:post].permit(:title, :body))
# flash[:notice] = "Your post is updated!"
# redirect_to @post
# else
# flash[:notice] = "There was an error updating your post."
# render :edit
# end
end
private
def post_params
params.require(:post).permit(:title, :body)
end
end
factories/post.rb
FactoryGirl.define do
factory :post do
title 'First title ever'
body 'Forage paleo aesthetic food truck. Bespoke gastropub pork belly, tattooed readymade chambray keffiyeh Truffaut ennui trust fund you probably haven\'t heard of them tousled.'
end
end
当前失败:
Failures:
1) PostsController#PUT update with success assigns the post to @post
Failure/Error: put :update, id: post.id
ArgumentError:
wrong number of arguments (0 for 1+)
# ./spec/controllers/posts_controller_spec.rb:86:in `block (4 levels) in <top (required)>'
Finished in 0.19137 seconds (files took 1.17 seconds to load)
17 examples, 1 failure
你绝对可以利用这里的工厂。
你创建的工厂其实也可以。
而不是做:
post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
这样做:post = FactoryGirl.create(:post)
如果你这样做,你可以获得更多的 DRY:
# in spec/rails_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
这将允许您在规范中执行此操作:post = create(:post)
关于你的 PUT 测试,试试这个 from a previous SO answer:
describe '#PUT update' do
let(:attr) do
{ :title => 'new title', :content => 'new content' }
end
context 'with success' do
before :each do
@post = FactoryGirl.create(:post)
end
it 'assigns the post to @post' do
put :update, :id => @post.id, :post => attr
@post.reload
expect(assigns(:post)).to eq(post)
end
end
end
编辑:
此外,如果需要,不要害怕将东西移到 before :each do
中。他们擅长保持干爽
您的规范失败的直接原因是您每次测试只能调用一次控制器,而对于更新,您调用了两次:在之前的操作中,您调用了 create... 和然后在更新测试的主要部分你调用更新...控制器规格不喜欢那样。
为了使现有规范正常工作,您需要将操作前的 post :create, post: attributes_for(:post)
行替换为仅创建 post 或(如前所述)使用工厂女孩来创建一个 post - 而不是尝试通过调用控制器来完成它。