Я пишу базовое приложение, используя RoR, и я тестирую с RSPec (и Factory Girl).Мое приложение работает как на Dev, так и на Prod, но я не могу пройти все мои тесты RSpec.Я подозреваю, что это какая-то особенность RSpec или что она связана с массовым назначением (виртуальных атрибутов).Я дам как можно больше информации, но, пожалуйста, дайте мне знать, если я опущу что-нибудь подходящее.(Некоторое время я занимался троллингом, но это мой первый пост, поэтому я уверен, что кое-что забуду.)
РЕДАКТИРОВАТЬ: я должен сказать, что все тесты проходили до того, как я внедрил виртуальныйатрибут (который я сделал, чтобы использовать автозаполнение JQuery-UI в этом поле имени пользователя).
Вот мои настройки:
Rails 3.1.1
Ruby 1.9.2p290
RSpec 2.6.4 (это то, что я получаю rspec -v из командной строки, ноGemfile говорит 2.6.1 - я не уверен, что с этим)
Factory Girl 1.0 (gem factory_girl_rails) Сайт разработчика - SQLite3
Prod размещен на heroku (postgres)
Во-первых, здесьявляется ошибочным результатом RSpec (на item_shares_controller.rb) - я включаю только один сбой для экономии места.Проблема одинакова при каждой неудаче (из 10).Я сосредоточен на этом, потому что я могу создать новый item_share на dev / prod, так что это обязательно должно пройти тест:
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).
Таким образом, проблема заключается в том, что контроллер не получает атрибут: receive_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 (I'mдля краткости не помещаем здесь весь контроллер):
#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 (где я определил receive_username как виртуальный атрибут, и вы можете увидеть методы setter / getter в нижней части модели):
# 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
А вот мой файл factories.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 "mhartl@example.com"
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] [: receive_username]) в контроллер (при тестировании).Я перепробовал несколько вещей и все еще в тупике.Вот что я попробовал:
- Я использую params_username.to_s.downcase в контроллере перед поиском пользователя.Я добавил это, и это помогло (RSpec провалил больше тестов, прежде чем я добавил это), но не решил эту проблему.
- Я пытался добавить: receive_username в строку attr_accessible в модели item_share.rb.
- Я пытался связываться с валидациями в модели item_share.rb (например, изменение строки validates: receive на validates_associated: receive и некоторые другие варианты такого рода вещей).
- rake db: test: prepare - не помогло
Как я уже говорил, все работает как на Dev, так и на Prod.Единственный признак того, что что-то «не так» - это сбои RSpec.
Есть идеи, как мне пройти эти тесты?
Спасибо
Джош