Lets set the stage for this question.
Here is our model:
class Deal < ActiveRecord::Base
belongs_to :retailer
has_many :content_locations, as: :locatable
has_many :stores, through: :content_locations
searchable do
text :title
text :store_name
text :store_sort_name
text :description
end
scope :by_keyword, ->(keyword, limit, offset) {
search {
fulltext keyword
paginate offset: offset, per_page: limit
}.results
}
end
Here is the section of our rspec test that hits SOLR:
describe 'searchable' do
before do
DatabaseCleaner.clean
end
target = 'foo'
let!(:deal_with_title) { create :deal, title: target }
let!(:deal_with_description) { create :deal, description: target }
let!(:deal_with_store_name) { create :deal, store_name: target }
let!(:deal_with_store_sort_name) { create :deal, store_sort_name: target }
let!(:deal_with_nothing) {
create :deal, title: 'bar', description: 'bar', store_name: 'bar', store_sort_name: 'bar'
}
it 'includes matches' do
sleep 1
results = Deal.by_keyword(target, 25, 0)
expect(results).to include(deal_with_title)
expect(results).to include(deal_with_description)
expect(results).to include(deal_with_store_name)
expect(results).to include(deal_with_store_sort_name)
expect(results).to_not include(deal_with_nothing)
end
end
What we are seeing is that our test fails randomly. I can execute this test over and over successfully for 1-3xs but the 4th will fail, or it will fail 4xs and the 5th will fail. The pattern is completely random. As you see we tried adding a sleep to this setup and that doesn't do anything but slow it down.
Any tips would be greatly appreciated here. This has been driving us nuts!
In the Sunspot Model Spec they're allways calling
Sunspot.commit
after data creation and before any check
Update:
You can also call model.index!, which will immediately sync the particular model into solr index. In one specific case i also had to call model.reload after saving and before indexing, because of some tag relationships attached to the model (however this was acts_as_taggable specific)
#simple
Factory.create(:model).tap { |m| m.index! }
#advanced
model = Factory.build :model_with_relationships
model.save
model.reload #optional
model.index!
We decided to go a completely different direction and honestly I would not recommend hitting SOLR in real time for anyone reading this post. It is a pain.
Our final solution was to with the sunspot_matchers gem instead.
Related
I'm trying to test if a specific attribute some_field gets assigned with correct values. I'm currently not persisting the records on the DB due to speed issues so I need a way thru stubs only.
Here's a sample method:
class Teacher < ApplicationRecord
has_many :students
def my_method
...
teacher.students.find_each do |s|
s.some_field[:foo] = 'bar'
s.save
end
end
end
Here's the test spec which is failing since :find_each only works with ActiveRecord Relations:
it 'assigns a correct value in some_field attribute' do
allow(teacher).to receive(:students).and_return([student1])
allow(student1).to receive(:save)
teacher.my_method
expect(student1.some_field).to eq({ foo: 'bar' })
end
Error:
NoMethodError:
undefined method `find_each' for #<Array:0x00007fa08a94b308>
I'm wondering if there's a way for this without persisting in DB?
Any help will be appreciated.
Also mock the find_each and return regular each instead.
let(:teacher) { double }
let(:students_mock) { double }
let(:student1) { double }
it do
expect(teacher).to receive(:students).and_return(students_mock)
expect(students_mock).to receive(:find_each) { |&block| [student1].each(&block) }
expect(student1).to receive(:save)
teacher.students.find_each(&:save)
end
In rspec-rails, I have an activerecord model called "Customer". Customer has_many "interactions". I recently spent quite a bit of time debugging something. I got it to work, but the answer I came up with does not really make sense to me.
The following code (utilizing RSpec's 'attribute of a subject') does not work:
its(:interactions) { should_receive(:create) }
but this works:
it "should call the 'create' method on interactions" do
subject.interactions.should_receive(:create)
end
Can anyone explain why this would be? Maybe I am misunderstanding how the shortened method syntax works. I have looked at the docs, but did not come up with any good reason.
Here is the complete code:
describe Customer do
...other code ...
describe "#migrate_interactions" do
context "when a customer successfully migrates interactions from a lead" do
subject { FactoryGirl.create(:customer) }
let(:lead) { mock_model(Lead) }
before :each do
#lead_interaction = { :user_id => 1,
:interaction_type => 'phone',
:notes => 'test',
:created_at => Time.now
}
lead.should_receive(:interactions).and_return([stub(#lead_interaction)])
end
its(:interactions) { should_receive(:create) }
its(:interactions) { should_receive(:create).with(hash_including(#lead_interaction)) }
after { subject.migrate_interactions lead }
end
end
end
And the model method in Customer.rb:
def migrate_interactions lead
raise ArgumentError unless lead.respond_to? :interactions
lead.interactions.each do |interaction|
self.interactions.create({ :user_id => interaction.user_id,
:interaction_type => interaction.interaction_type,
:notes => interaction.notes,
:created_at => interaction.created_at })
end
end
Thanks!
----------edit---------
I forgot to include the error that came up when I used the its(:interactions) { ... } syntax.
Here is the error:
1) Customer#migrate_interactions when a customer migrates interactions from a lead interactions
Failure/Error: its(:interactions) { should_receive(:create) }
(#<RSpec::Core::ExampleGroup::Nested_2::Nested_6::Nested_3::Nested_1:0x0000000af10e78>).create(any args)
expected: 1 times
received: 0 times
# ./spec/models/customer_spec.rb:100:in `block (4 levels) in <top (required)>'
What you're trying to do can not work because RSpec's one-liner syntax (e.g. it { should do_something }) supports should but does not support should_receive. It's something I never thought to add because it doesn't align with my approach to message expectations, and I don't think it ever came up in a feature request.
There is a new syntax alternative in development that reads expect(object).to receive(message), which might open the door to it { should receive(message) }, but I'm not sure (I'm not running the project any longer and I haven't looked at that code in detail). If you're interested, take a look at (and join the conversation) https://github.com/rspec/rspec-mocks/issues/153.
HTH,
David
I'm newbie in RoR, and I am trying to test a simple named_scope for my Model.
But I don't know if I have a problem in my model (I'm using mongoid), in my code test (I'm using rspec) or in my factory. I got this error
Mongoid::Errors::InvalidCollection:
Access to the collection for Movement is not allowed since it is an embedded document, please access a collection from the root
document.
My models
class Movement
include Mongoid::Document
field :description, :type => String
embedded_in :category
named_scope :top, lambda { |number| { :limit => (number.size > 0 ? number : 10) } }
end
class Category
include Mongoid::Document
field :name
embeds_many :movement
end
My factory, con factory_girl
Factory.define :movement do |m|
m.amount 24
m.date "30/10/2011"
m.description "Beer"
m.association :category, :factory => :category
end
Factory.define :category do |c|
c.name "Drink"
end
My test
describe "when i have a movement list" do
it "recent method should return last 2 movements" do
#movements = (1..3).collect { Factory(:movement) }
recent_movements = Movement.top(2)
recent_movements.should have(2).entries
end
end
And the error:
Mongoid::Errors::InvalidCollection:
Access to the collection for Movement is not allowed since it is an embedded >document,
please access a collection from the root document.
I tried a little change in my factory.
Factory.define :movement do |m|
m.amount 24
m.date "30/10/2011"
m.description "Beer"
m.category { [ Factory.build(:category) ] }
end
But then I got other different error:
Failure/Error: #movements = (1..3).collect { Factory(:movement) }
NoMethodError:
undefined method `reflect_on_association' for #
Could someone help me?
Thanks
I just had a the same error in my app. I ended up having an error in my class and that solved my problem.
I have a Code model factory like this:
Factory.define :code do |f|
f.value "code"
f.association :code_type
f.association(:codeable, :factory => :portfolio)
end
But when I test my controller with a simple test_should_create_code like this:
test "should create code" do
assert_difference('Code.count') do
post :create, :code => Factory.attributes_for(:code)
end
assert_redirected_to code_path(assigns(:code))
end
... the test fails. The new record is not created.
In the console, it seems that attributes_for does not return all required attributes like the create does.
rob#compy:~/dev/my_rails_app$ rails console test
Loading test environment (Rails 3.0.3)
irb(main):001:0> Factory.create(:code)
=> #<Code id: 1, code_type_id: 1, value: "code", codeable_id: 1, codeable_type: "Portfolio", created_at: "2011-02-24 10:42:20", updated_at: "2011-02-24 10:42:20">
irb(main):002:0> Factory.attributes_for(:code)
=> {:value=>"code"}
Any ideas?
Thanks,
You can try something like this:
(Factory.build :code).attributes.symbolize_keys
Check this: http://groups.google.com/group/factory_girl/browse_thread/thread/a95071d66d97987e)
This one doesn't return timestamps etc., only attributes that are accessible for mass assignment:
(FactoryGirl.build :position).attributes.symbolize_keys.reject { |key, value| !Position.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Still, it's quite ugly. I think FactoryGirl should provide something like this out of the box.
I opened a request for this here.
I'd suggest yet an other approach, which I think is clearer:
attr = attributes_for(:code).merge(code_type: create(:code_type))
heres what I end up doing...
conf = FactoryGirl.build(:conference)
post :create, {:conference => conf.attributes.slice(*conf.class.accessible_attributes) }
I've synthesized what others have said, in case it helps anyone else. To be consistent with the version of FactoryGirl in question, I've used Factory.build() instead of FactoryGirl.build(). Update as necessary.
def build_attributes_for(*args)
build_object = Factory.build(*args)
build_object.attributes.slice(*build_object.class.accessible_attributes).symbolize_keys
end
Simply call this method in place of Factory.attributes_for:
post :create, :code => build_attributes_for(:code)
The full gist (within a helper module) is here: https://gist.github.com/jlberglund/5207078
In my APP/spec/controllers/pages_controllers_spec.rb I set:
let(:valid_attributes) { FactoryGirl.attributes_for(:page).merge(subject: FactoryGirl.create(:theme), user: FactoryGirl.create(:user)) }
Because I have two models associated. This works too:
FactoryGirl.define do
factory :page do
title { Faker::Lorem.characters 12 }
body { Faker::Lorem.characters 38 }
discution false
published true
tags "linux, education, elearning"
section { FactoryGirl.create(:section) }
user { FactoryGirl.create(:user) }
end
end
Here's another way. You probably want to omit the id, created_at and updated_at attributes.
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.
I am reading the book Simply Rails by Sitepoint and given these models:
story.rb
class Story < ActiveRecord::Base
validates_presence_of :name, :link
has_many :votes do
def latest
find :all, :order => 'id DESC', :limit => 3
end
end
def to_param
"#{id}-#{name.gsub(/\W/, '-').downcase}"
end
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :story
end
and given this fixtures
stories.yml
one:
name: MyString
link: MyString
two:
name: MyString2
link: MyString2
votes.yml
one:
story: one
two:
story: one
these tests fail:
story_test.rb
def test_should_have_a_votes_association
assert_equal [votes(:one),votes(:two)], stories(:one).votes
end
def test_should_return_highest_vote_id_first
assert_equal votes(:two), stories(:one).votes.latest.first
end
however, if I reverse the order of the stories, for the first assertion and provide the first vote for the first assertion, it passes
story_test.rb
def test_should_have_a_votes_association
assert_equal [votes(:two),votes(:one)], stories(:one).votes
end
def test_should_return_highest_vote_id_first
assert_equal votes(:one), stories(:one).votes.latest.first
end
I copied everything as it is in the book and have not seen an errata about this. My first conclusion was that the fixture is creating the records from bottom to top as it was declared, but that doesn't make any point
any ideas?
EDIT: I am using Rails 2.9 running in an RVM
Your fixtures aren't getting IDs 1, 2, 3, etc. like you'd expect - when you add fixtures, they get IDs based (I think) on a hash of the table name and the fixture name. To us humans, they just look like random numbers.
Rails does this so you can refer to other fixtures by name easily. For example, the fixtures
#parents.yml
vladimir:
name: Vladimir Ilyich Lenin
#children.yml
joseph:
name: Joseph Vissarionovich Stalin
parent: vladimir
actually show up in your database like
#parents.yml
vladimir:
id: <%= fixture_hash('parents', 'vladimir') %>
name: Vladimir Ilyich Lenin
#children.yml
joseph:
id: <%= fixture_hash('children', 'joseph') %>
name: Joseph Vissarionovich Stalin
parent_id: <%= fixture_hash('parents', 'vladimir') %>
Note in particular the expansion from parent: vladimir to parent_id: <%= ... %> in the child model - this is how Rails handles relations between fixtures.
Moral of the story: Don't count on your fixtures being in any particular order, and don't count on :order => :id giving you meaningful results in tests. Use results.member? objX repeatedly instead of results == [obj1, obj2, ...]. And if you need fixed IDs, hard-code them in yourself.
Hope this helps!
PS: Lenin and Stalin weren't actually related.
Xavier Holt already gave the main answer, but wanted to also mention that it is possible to force rails to read in fixtures in a certain order.
By default rails assigns its own IDs, but you can leverage the YAML omap specification to specify an ordered mapping
# countries.yml
--- !omap
- netherlands:
id: 1
title: Kingdom of Netherlands
- canada:
id: 2
title: Canada
Since you are forcing the order, you have to also specify the ID yourself manually, as shown above.
Also I'm not sure about this part, but I think once you commit to overriding the default rails generated ID and use your own, you have to do the same for all downstream references.
In the above example, suppose each country can have multiple leaders, you would have do something like
# leaders.yml
netherlands-leader:
country_id: 1 #you have to specify this now!
name: Willem-Alexander
You need to manually specify the id that refers to the previous Model (Countries)