开发和生产工作时 RSpec 测试失败 - RSpec 怪癖、虚拟属性或表单问题?

发布于 2024-12-21 20:18:13 字数 7211 浏览 0 评论 0原文

我正在使用 RoR 编写一个基本应用程序,并且正在使用 RSPec(和 Factory Girl)进行测试。我的应用程序可以在开发和生产环境中运行,但我无法通过所有 RSpec 测试。我怀疑这是某种 RSpec 怪癖,或者它与(虚拟属性的)批量分配有关。我将提供尽可能多的信息,但如果我遗漏了任何相关内容,请告诉我。 (我已经拖钓了一段时间,但这是我的第一篇文章,所以我确信我会忘记一些东西。)

编辑:我应该说在我实现虚拟属性之前所有测试都已通过(我这样做是为了在此用户名字段上使用 JQuery-ui 自动完成功能)。

这是我的设置:

Rails 3.1.1
红宝石 1.9.2p290
RSpec 2.6.4(这就是我从命令行执行 rspec -v 的结果,但 Gemfile 说是 2.6.1 - 我不确定这是怎么回事)
工厂女孩 1.0 (factory_girl_rails gem) 开发站点是 SQLite3
Prod 托管在 heroku (postgres) 上,

首先,这是 RSpec 的失败结果(在 item_shares_controller.rb 上) - 我只包含一个失败以节省空间。每次失败(共 10 次)的问题都是相同的。我专注于这一点,因为我能够在 dev/prod 上创建一个新的 item_share,所以这肯定会通过测试:

  5) ItemSharesController POST 'create' success should create an item_share
     Failure/Error: post :create, @attr
     ActiveRecord::RecordInvalid:
       Validation failed: Receiver is required. Please enter a valid username.
     # ./app/controllers/item_shares_controller.rb:25:in `create'
     # ./spec/controllers/item_shares_controller_spec.rb:71:in `block (5 levels) in <top (required)>'
     # ./spec/controllers/item_shares_controller_spec.rb:70:in `block (4 levels) in <top (required)>'

注意:我已经在下面的代码中标记了相关行(25, 70, 71 )。

所以问题似乎是控制器没有从我的表单中获取 :receiver_username 属性。格式如下:

<%= form_for(@item_share) do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  <div class="field">
    <%= fields_for :item do |i| %>
      <%= i.label "What?" %></br>
      <%= i.text_field :link_url, { :placeholder => 'http://...' }  %>
    <% end %>
  </div>
  <div class="field">
      <%= f.label "With who?" %></br>
      <%= f.text_field :receiver_username, :placeholder => 'JohnDoe123', data: { autocomplete_source: autocomplete_users_path } %>
  </div>
  <div class = "field">
    <%= f.label "Why?" %></br>
    <%= f.text_area :note, { :placeholder => '(Optional) In 140 characters or fewer, add a note.' }  %>
  </div>
  <div class="actions">
    <%= f.submit "Submit" %>
  </div>
<% end %>

请注意,此处的相关字段是 :receiver_username 字段,它将把用户名传递给控制器​​。该字段是 item_share 模型的虚拟属性(item_share.rb 如下),您可以看到我在该字段上使用 jQuery-ui 的自动完成功能。

这是控制器 :create 方法(为了简洁起见,我没有将整个控制器放在这里):

#item_shares_controller.rb

def create

  @item = Item.new()
  @item.link_url = params[:item] ? params[:item][:link_url] : ""


  params_username = params[:item_share] ? params[:item_share][:receiver_username] : ""

  @receiver = User.find(:first, :conditions => [ "lower(username) = ?", params_username.to_s.downcase ] )

  @giver = current_user

  @note = params[:item_share] ? params[:item_share][:note] : ""

  @item_share = current_user.item_shares_given.build(:item => @item, :receiver => @receiver, :note => @note)

  # The next line is 25 as called out in the failure
  if  @item_share.save!()
    if @receiver.send_email? && !(@receiver == @giver)
      UserMailer.new_share_notification(@receiver, @giver, @item_share).deliver
    end
    flash[:success] = "You shared a link!"
    redirect_to root_path
  else
    @list_items = []
    flash.now[:error] = "The link was not shared."
    render 'pages/home'
  end

end

这是 item_share 模型(其中我将receiver_username 定义为虚拟属性,您可以在控制器底部看到 setter/getter 方法) model):

# item_share.rb

class ItemShare < ActiveRecord::Base
  attr_accessible :note, :item, :receiver, :share_source_user_id, :share_target_user_id, :item_id

  # ItemShares relation to Users
  belongs_to :giver, :class_name => "User",
    :foreign_key => "share_source_user_id"
  belongs_to :receiver, :class_name => "User",
    :foreign_key => "share_target_user_id"

  # ItemShares relation to Items
  belongs_to :item

  default_scope :order => 'item_shares.created_at DESC'

  validates :note, :length => { :within => 0..140, :message => "must be 140 characters or fewer." } 
  validates_associated :item, :message => "is not valid. Please enter a valid URL."
  validates :receiver, :presence => {:message => "is required. Please enter a valid username." }
  validates :share_source_user_id, :presence => true

  def receiver_username
    receiver.try(:username)
  end

  def receiver_username=(username)
    self.receiver = Receiver.find_by_username(username) if username.present?
  end

end

这是我的一些 item_shares_controller_spec.rb 文件(我尝试对其进行修剪以显示相关内容,但我没有包含整个内容,再次用于简洁):

# item_shares_controller_spec.rb

require 'spec_helper'

describe ItemSharesController do
  render_views

  describe "POST 'create'" do

    before(:each) do
      @user = test_sign_in(Factory(:user))
    end

    describe "success" do

      before(:each) do
        @receiver = Factory(:user, :name => Factory.next(:name),
                            :username => Factory.next(:username),
                            :email => Factory.next(:email))
        @item = Factory(:item)
        @attr = {:item =>{:link_url=>@item.link_url}, :item_share =>{:receiver_username=>@receiver.username}, :item_share=>{:note=>"Note"}}
      end

      it "should create an item_share" do
        # Next lines are 70 and 71 as called out in the failure
        lambda do
          post :create, @attr
        end.should change(ItemShare, :count).by(1)
      end
    end
  end
end

这是我的factory.rb 文件(我认为这与问题无关,但我不妨将其包括在内):

#factories.rb

# By using the symbol ':user', we get Factory Girl to simulate the User model.
Factory.define :user do |user|
  user.name                  "Michael Hartl"
  user.username              "MichaelHartl"
  user.email                 "[email protected]"
  user.password              "foobar"
  user.password_confirmation "foobar"
  user.send_email            true
end

Factory.sequence :name do |n|
  result = "PersonAAA"
  n.times { result.succ! }
  result
end

Factory.sequence :email do |n|
  "person-#{n}@example.com"
end

Factory.sequence :username do |n|
  "Person#{n}"
end

Factory.define :item do |item|
  item.link_url              "http://www.google.com"
end

Factory.define :item_share do |item_share|
  item_share.note                  "Foo bar"
  item_share.association           :giver, :factory => :user
  item_share.association           :receiver, :factory => :user
  item_share.association           :item, :factory => :item
end

好的,那么回到失败。由于某种原因,RSpec 似乎无法将用户名(我希望位于 params[:item_share][:receiver_username] 中)传递给控制器​​(测试时)。我已经尝试了几件事,但仍然感到困惑。这是我尝试过的:

  • 我在控制器中使用 params_username.to_s.downcase 之前 搜索用户。我添加了这一点,它有帮助(RSpec 失败了 在我添加之前进行了更多测试),但没有解决这个问题。
  • 我尝试将 :receiver_username 添加到 attr_accessible 行中 item_share.rb 模型。
  • 我尝试过搞乱验证 item_share.rb 模型(例如,更改 validates :receiver 行 到 validates_linked :receiver 以及其他一些变体 之类的事情)。
  • rake db:test:prepare - 没有帮助

正如我所说,一切都在开发和生产中运行。唯一表明出现“错误”的情况是 RSpec 故障。

有什么想法可以让这些测试通过吗?

谢谢乔希

I'm writing a basic application using RoR, and I'm testing with RSPec (and Factory Girl). My app is working on both Dev and Prod, but I can't get all of my RSpec tests to pass. I suspect this is some sort of RSpec quirk or that it's related to mass assignment (of virtual attributes). I'll give as much information as possible, but please let me know if I leave out anything pertinent. (I've been trolling for a while, but this is my first post, so I'm sure I'll forget something.)

EDIT: I should say that all tests were passing before I implemented the virtual attribute (which I did to use the JQuery-ui autocomplete on this username field).

Here's my setup:

Rails 3.1.1
Ruby 1.9.2p290
RSpec 2.6.4 (that's what I get doing rspec -v from the command line, but the Gemfile says 2.6.1 - I'm not sure what's up with that)
Factory Girl 1.0 (factory_girl_rails gem)
Dev site is SQLite3
Prod is hosted on heroku (postgres)

First, here is a failing result from RSpec (on item_shares_controller.rb) - I'm only including one failure to save space. The issue is the same on each failure (of 10). I'm focused on this one because it I'm able to create a new item_share on dev/prod, so this should definitely pass the test:

  5) ItemSharesController POST 'create' success should create an item_share
     Failure/Error: post :create, @attr
     ActiveRecord::RecordInvalid:
       Validation failed: Receiver is required. Please enter a valid username.
     # ./app/controllers/item_shares_controller.rb:25:in `create'
     # ./spec/controllers/item_shares_controller_spec.rb:71:in `block (5 levels) in <top (required)>'
     # ./spec/controllers/item_shares_controller_spec.rb:70:in `block (4 levels) in <top (required)>'

NOTE: I've marked the relevant lines in the code below (25, 70, 71).

So the issue seems to be that the controller isn't getting the :receiver_username attribute from my form. Here's the form:

<%= form_for(@item_share) do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  <div class="field">
    <%= fields_for :item do |i| %>
      <%= i.label "What?" %></br>
      <%= i.text_field :link_url, { :placeholder => 'http://...' }  %>
    <% end %>
  </div>
  <div class="field">
      <%= f.label "With who?" %></br>
      <%= f.text_field :receiver_username, :placeholder => 'JohnDoe123', data: { autocomplete_source: autocomplete_users_path } %>
  </div>
  <div class = "field">
    <%= f.label "Why?" %></br>
    <%= f.text_area :note, { :placeholder => '(Optional) In 140 characters or fewer, add a note.' }  %>
  </div>
  <div class="actions">
    <%= f.submit "Submit" %>
  </div>
<% end %>

Note that the relevant field here is the :receiver_username field, which will pass the username to the controller. This field is a virtual attribute of the item_share model (item_share.rb is below) and you can see I'm using jQuery-ui's autocomplete function on that field.

Here's the controller :create method (I'm not putting the whole controller here for brevity):

#item_shares_controller.rb

def create

  @item = Item.new()
  @item.link_url = params[:item] ? params[:item][:link_url] : ""


  params_username = params[:item_share] ? params[:item_share][:receiver_username] : ""

  @receiver = User.find(:first, :conditions => [ "lower(username) = ?", params_username.to_s.downcase ] )

  @giver = current_user

  @note = params[:item_share] ? params[:item_share][:note] : ""

  @item_share = current_user.item_shares_given.build(:item => @item, :receiver => @receiver, :note => @note)

  # The next line is 25 as called out in the failure
  if  @item_share.save!()
    if @receiver.send_email? && !(@receiver == @giver)
      UserMailer.new_share_notification(@receiver, @giver, @item_share).deliver
    end
    flash[:success] = "You shared a link!"
    redirect_to root_path
  else
    @list_items = []
    flash.now[:error] = "The link was not shared."
    render 'pages/home'
  end

end

Here is the item_share model (where I've defined receiver_username as a virtual attribute and you can see the setter/getter methods at the bottom of the model):

# item_share.rb

class ItemShare < ActiveRecord::Base
  attr_accessible :note, :item, :receiver, :share_source_user_id, :share_target_user_id, :item_id

  # ItemShares relation to Users
  belongs_to :giver, :class_name => "User",
    :foreign_key => "share_source_user_id"
  belongs_to :receiver, :class_name => "User",
    :foreign_key => "share_target_user_id"

  # ItemShares relation to Items
  belongs_to :item

  default_scope :order => 'item_shares.created_at DESC'

  validates :note, :length => { :within => 0..140, :message => "must be 140 characters or fewer." } 
  validates_associated :item, :message => "is not valid. Please enter a valid URL."
  validates :receiver, :presence => {:message => "is required. Please enter a valid username." }
  validates :share_source_user_id, :presence => true

  def receiver_username
    receiver.try(:username)
  end

  def receiver_username=(username)
    self.receiver = Receiver.find_by_username(username) if username.present?
  end

end

And here's some of my item_shares_controller_spec.rb file (I've tried to trim it to show what's relevant, but I'm not including the whole thing, again for brevity):

# item_shares_controller_spec.rb

require 'spec_helper'

describe ItemSharesController do
  render_views

  describe "POST 'create'" do

    before(:each) do
      @user = test_sign_in(Factory(:user))
    end

    describe "success" do

      before(:each) do
        @receiver = Factory(:user, :name => Factory.next(:name),
                            :username => Factory.next(:username),
                            :email => Factory.next(:email))
        @item = Factory(:item)
        @attr = {:item =>{:link_url=>@item.link_url}, :item_share =>{:receiver_username=>@receiver.username}, :item_share=>{:note=>"Note"}}
      end

      it "should create an item_share" do
        # Next lines are 70 and 71 as called out in the failure
        lambda do
          post :create, @attr
        end.should change(ItemShare, :count).by(1)
      end
    end
  end
end

And here's my factories.rb file (I don't think this is related to the issue, but I might as well include it):

#factories.rb

# By using the symbol ':user', we get Factory Girl to simulate the User model.
Factory.define :user do |user|
  user.name                  "Michael Hartl"
  user.username              "MichaelHartl"
  user.email                 "[email protected]"
  user.password              "foobar"
  user.password_confirmation "foobar"
  user.send_email            true
end

Factory.sequence :name do |n|
  result = "PersonAAA"
  n.times { result.succ! }
  result
end

Factory.sequence :email do |n|
  "person-#{n}@example.com"
end

Factory.sequence :username do |n|
  "Person#{n}"
end

Factory.define :item do |item|
  item.link_url              "http://www.google.com"
end

Factory.define :item_share do |item_share|
  item_share.note                  "Foo bar"
  item_share.association           :giver, :factory => :user
  item_share.association           :receiver, :factory => :user
  item_share.association           :item, :factory => :item
end

Ok, so back to the failure. It looks like RSpec isn't able to pass the username (which I would expect to be in params[:item_share][:receiver_username]) to the controller (when testing) for some reason. I've tried several things and am still stumped. Here's what I've tried:

  • I'm using a params_username.to_s.downcase in the controller before I
    search for the User. I added that and it helped (RSpec was failing
    more tests before I added that), but didn't resolve this issue.
  • I've tried adding :receiver_username to the attr_accessible line in
    the item_share.rb model.
  • I've tried messing with the validations in
    the item_share.rb model (e.g., changing the validates :receiver line
    to a validates_associated :receiver and some other variations on that
    sort of thing).
  • rake db:test:prepare - didn't help

As I said, everything is working on both Dev and Prod. The only indication that something is "wrong" is with the RSpec failures.

Any ideas how I can get these tests to pass?

Thanks

Josh

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

滥情哥ㄟ 2024-12-28 20:18:13

我将此作为对我原来问题的评论添加,但这显然并不将问题本身标记为“已回答”。对于后代,这是我的答案的复制/粘贴:

我想我发现了问题 - 我没有在我的 @attr 变量中正确定义参数。我

# item_shares_controller_spec.rb
describe ItemSharesController do
  render_views

  describe "POST 'create'" do

    before(:each) do
      @user = test_sign_in(Factory(:user))
    end

    describe "success" do

      before(:each) do
        @attr = {:item =>{:link_url=>@item.link_url}, :item_share =>{:receiver_username=>@receiver.username}, :item_share=>{:note=>"Note"}} 

……当我应该有的时候

@attr = {:item =>{:link_url=>@link_url}, :item_share=>{:receiver_username=>@receiver.username, :note=>"Note"}} 

请注意,我在 @attr 定义中定义了 :item_share 两次。我认为 RSpec 忽略了第一个 :item_share 参数,只采用第二个 :note 参数。

I added this as a comment to my original question, but that obviously doesn't mark the question itself as "answered". For posterity, here's a copy/paste of my answer:

I think I found the issue - I wasn't defining the parameters correctly in my @attr variable. I had

# item_shares_controller_spec.rb
describe ItemSharesController do
  render_views

  describe "POST 'create'" do

    before(:each) do
      @user = test_sign_in(Factory(:user))
    end

    describe "success" do

      before(:each) do
        @attr = {:item =>{:link_url=>@item.link_url}, :item_share =>{:receiver_username=>@receiver.username}, :item_share=>{:note=>"Note"}} 

...when I should have had

@attr = {:item =>{:link_url=>@link_url}, :item_share=>{:receiver_username=>@receiver.username, :note=>"Note"}} 

Note that I was defining :item_share twice within the @attr definition. I think RSpec was ignoring the first :item_share parameter and taking only the second one, :note.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文