rails+rspec 具有多态关联的控制器规范
rails+rspec controller spec with polymorphic association
我正在尝试测试控制器规范中的操作,但出于某种原因,我收到了“无路由匹配”错误。我应该怎么做才能使路线正常运行?
ActionController::UrlGenerationError:
No route matches {:action=>"create", :comment=>{:body=>"Consectetur quo accusamus ea.",
:commentable=>"4"}, :controller=>"comments", :post_id=>"4"}
型号
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true, touch: true
class Post < ActiveRecord::Base
has_many :comments, as: :commentable, dependent: :destroy
路线
resources :posts do
resources :comments, only: [:create, :update, :destroy], module: :posts
end
controller_spec
describe "POST create" do
let!(:user) { create(:user) }
let!(:profile) { create(:profile, user: @user) }
let!(:commentable) { create(:post, user: @user) }
context "with valid attributes" do
subject(:create_action) { xhr :post, :create, post_id: commentable, comment: attributes_for(:comment, commentable: commentable, user: @user) }
it "saves the new task in the db" do
expect{ create_action }.to change{ Comment.count }.by(1)
end
...
编辑
上面的 controller_spec 可以在 spec/controllers/comments_controller_spec.rb
中找到
controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
@comment = @commentable.comments.new(comment_params)
authorize @comment
@comment.user = current_user
if @comment.save
@comment.send_comment_creation_notification(@commentable)
respond_to :js
end
end
controllers/posts/comments_controller.rb
class Posts::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
@commentable = Post.find(params[:post_id])
end
使用 module: :posts
将路由到 Posts::CommentsController#create
。
如果这不是您想要的,请删除模块选项。
否则您需要确保您的控制器和规范的 class 名称正确。
class Posts::CommentsController
def create
end
end
RSpec.describe Posts::CommentsController do
# ...
end
另请注意,嵌套资源的 "individual actions" 通常没有意义。
相反,您可能希望像这样声明路由:
resources :comments, only: [:update, :destroy] # show, edit ...
resources :posts do
resources :comments, only: [:create], module: :posts # new, index
end
这给你:
class CommentsController < ApplicationController
before_action :set_posts
# DELETE /comments/:id
def destroy
# ...
end
# PUT|PATCH /comments/:id
def update
end
end
class Posts::CommentsController < ApplicationController
# POST /posts/:post_id/comments
def create
# ...
end
end
有关原因的更深入解释,请参阅 Avoid Deeply Nested Routes in Rails。
在这种情况下将控制器设置为使用继承是一个好主意 - 但是您无法通过控制器规范中的父 CommentsController
class 测试 create
方法,因为RSpec 在尝试解析路由时将始终查看 described_class
。
相反,您可能想使用共享示例:
# /spec/support/shared_examples/comments.rb
RSpec.shared_examples "nested comments controller" do |parameter|
describe "POST create" do
let!(:user) { create(:user) }
context "with valid attributes" do
subject(:create_action) { xhr :post, :create, post_id: commentable, comment: attributes_for(:comment, commentable: commentable, user: @user) }
it "saves the new task in the db" do
expect{ create_action }.to change{ Comment.count }.by(1)
end
end
end
end
require 'rails_helper'
require 'shared_examples/comments'
RSpec.describe Posts::CommentsController
# ...
include_examples "nested comments controller" do
let(:commentable) { create(:post, ...) }
end
end
require 'rails_helper'
require 'shared_examples/comments'
RSpec.describe Products::CommentsController
# ...
include_examples "nested comments controller" do
let(:commentable) { create(:product, ...) }
end
end
我更喜欢的另一种选择是改用请求规范:
require 'rails_helper'
RSpec.describe "Comments", type: :request do
RSpec.shared_example "has nested comments" do
let(:path) { polymorphic_path(commentable) + "/comments" }
let(:params) { attributes_for(:comment) }
describe "POST create" do
expect do
xhr :post, path, params
end.to change(commentable.comments, :count).by(1)
end
end
context "Posts" do
include_examples "has nested comments" do
let(:commentable) { create(:post) }
end
end
context "Products" do
include_examples "has nested comments" do
let(:commentable) { create(:product) }
end
end
end
由于您实际上是在发送 HTTP 请求而不是伪造它,因此它们涵盖了更多的应用程序堆栈。然而,就测试速度而言,这确实要付出很小的代价。 shared_context 和 shared_examples 都是让 RSpec 非常棒的两件事。
我正在尝试测试控制器规范中的操作,但出于某种原因,我收到了“无路由匹配”错误。我应该怎么做才能使路线正常运行?
ActionController::UrlGenerationError:
No route matches {:action=>"create", :comment=>{:body=>"Consectetur quo accusamus ea.",
:commentable=>"4"}, :controller=>"comments", :post_id=>"4"}
型号
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true, touch: true
class Post < ActiveRecord::Base
has_many :comments, as: :commentable, dependent: :destroy
路线
resources :posts do
resources :comments, only: [:create, :update, :destroy], module: :posts
end
controller_spec
describe "POST create" do
let!(:user) { create(:user) }
let!(:profile) { create(:profile, user: @user) }
let!(:commentable) { create(:post, user: @user) }
context "with valid attributes" do
subject(:create_action) { xhr :post, :create, post_id: commentable, comment: attributes_for(:comment, commentable: commentable, user: @user) }
it "saves the new task in the db" do
expect{ create_action }.to change{ Comment.count }.by(1)
end
...
编辑
上面的 controller_spec 可以在 spec/controllers/comments_controller_spec.rb
controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
@comment = @commentable.comments.new(comment_params)
authorize @comment
@comment.user = current_user
if @comment.save
@comment.send_comment_creation_notification(@commentable)
respond_to :js
end
end
controllers/posts/comments_controller.rb
class Posts::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
@commentable = Post.find(params[:post_id])
end
使用 module: :posts
将路由到 Posts::CommentsController#create
。
如果这不是您想要的,请删除模块选项。
否则您需要确保您的控制器和规范的 class 名称正确。
class Posts::CommentsController
def create
end
end
RSpec.describe Posts::CommentsController do
# ...
end
另请注意,嵌套资源的 "individual actions" 通常没有意义。
相反,您可能希望像这样声明路由:
resources :comments, only: [:update, :destroy] # show, edit ...
resources :posts do
resources :comments, only: [:create], module: :posts # new, index
end
这给你:
class CommentsController < ApplicationController
before_action :set_posts
# DELETE /comments/:id
def destroy
# ...
end
# PUT|PATCH /comments/:id
def update
end
end
class Posts::CommentsController < ApplicationController
# POST /posts/:post_id/comments
def create
# ...
end
end
有关原因的更深入解释,请参阅 Avoid Deeply Nested Routes in Rails。
在这种情况下将控制器设置为使用继承是一个好主意 - 但是您无法通过控制器规范中的父 CommentsController
class 测试 create
方法,因为RSpec 在尝试解析路由时将始终查看 described_class
。
相反,您可能想使用共享示例:
# /spec/support/shared_examples/comments.rb
RSpec.shared_examples "nested comments controller" do |parameter|
describe "POST create" do
let!(:user) { create(:user) }
context "with valid attributes" do
subject(:create_action) { xhr :post, :create, post_id: commentable, comment: attributes_for(:comment, commentable: commentable, user: @user) }
it "saves the new task in the db" do
expect{ create_action }.to change{ Comment.count }.by(1)
end
end
end
end
require 'rails_helper'
require 'shared_examples/comments'
RSpec.describe Posts::CommentsController
# ...
include_examples "nested comments controller" do
let(:commentable) { create(:post, ...) }
end
end
require 'rails_helper'
require 'shared_examples/comments'
RSpec.describe Products::CommentsController
# ...
include_examples "nested comments controller" do
let(:commentable) { create(:product, ...) }
end
end
我更喜欢的另一种选择是改用请求规范:
require 'rails_helper'
RSpec.describe "Comments", type: :request do
RSpec.shared_example "has nested comments" do
let(:path) { polymorphic_path(commentable) + "/comments" }
let(:params) { attributes_for(:comment) }
describe "POST create" do
expect do
xhr :post, path, params
end.to change(commentable.comments, :count).by(1)
end
end
context "Posts" do
include_examples "has nested comments" do
let(:commentable) { create(:post) }
end
end
context "Products" do
include_examples "has nested comments" do
let(:commentable) { create(:product) }
end
end
end
由于您实际上是在发送 HTTP 请求而不是伪造它,因此它们涵盖了更多的应用程序堆栈。然而,就测试速度而言,这确实要付出很小的代价。 shared_context 和 shared_examples 都是让 RSpec 非常棒的两件事。