I've wrote the follow example:
it "should assign #services containing all the current user services" do
customer = FactoryGirl.create(:user, fullname: "Iris Steensma")
sign_in customer
service = FactoryGirl.create(:service, user: customer)
puts "Service.count = #{Service.count}" # Service.count = 0
get :home
assigns[:services].should eq([service])
end
The action controller as:
def home
##services = curent_user.posted_services
#services = Service.all
end
And factories.rb contains:
FactoryGirl.define do
sequence :address do |n|
"Street #{n}"
end
factory :user do
fullname "Foo Bar"
email { "#{fullname.gsub(' ', '.').downcase}#example.com" if fullname }
password "secret"
end
factory :preference do
profile "customer"
user
end
factory :service do
status :pending
source_addr { generate(:address) }
target_addr { generate(:address) }
passenger "Mis Daysi"
start_at Time.now
offer 5
payment "cash"
user
end
end
Why Factory Girl can't create the Service record? The factory works fine in the test environment "rails c test"
Here is the rspec ouput:
Failures:
1) UsersController GET home should assign #services containing all the current user services
Failure/Error: assigns[:services].should eq([service])
expected: [#<Service:0x460d8ea #name="Service_1003">]
got: []
(compared using ==)
Diff:
## -1,2 +1,2 ##
-[#<Service:0x460d8ea #name="Service_1003">]
+[]
# ./spec/controllers/users_controller_spec.rb:26:in `block (3 levels) in <top (required)>'
Finished in 1.03 seconds
3 examples, 1 failure
I believe the correct syntax is:
assigns(:service).should eq([service])
According to the rspec documentation assigns[key] used to be the way to assign instance variables which looks a little like what's happening for you.
https://github.com/rspec/rspec-rails
First, the user: customer here is not needed, since you are using customer.services.create that already does that
service = customer.services.create(attributes_for(:service, user: customer))
Second, try this, after service = customer.services.create do something like
service.valid?
puts service.errors.inspect
puts service.user == customer
maybe you can also try
service = FactoryGirl.create(:service, :user => customer)
also, are you sure the association is defined on Service class?
doesn't
#<Service:0x57856f4 #name="Service_1003">
should be
#<Service:0x57856f4 #name="Service_1003" user="<your customer object">
?
how is your factory for service defined?
Related
I'm trying to test this after_initialize callback which is for the item model (which has_many line_items):
after_initialize :build_default_items, unless: :line_items?
callback:
def build_default_items
LineOfBusiness.all.each do |lob|
line_items.new(line_of_business_id: lob.id)
end
end
My test looks like:
describe 'callbacks' do
let(:user) { create :user }
it 'should build default items' do
lob1 = LineOfBusiness.create(id:1, name: "Name1", eff_date: Date.today,exp_date: Date.tomorrow, create_user: user, update_user: user)
lob2 = LineOfBusiness.create(id:2, name: "Name2", eff_date: Date.today,exp_date: Date.tomorrow, create_user: user, update_user: user)
lob_count = LineOfBusiness.all.count # this is correct as 2
item = build :item
expect(item.line_items.count).to eq(lob_count)
end
end
Error message as follows:
expected: 2
got: 0
(compared using ==)
So its failing in the callback method, its seeing the LineOfBusiness.all as Nil
def build_default_items
LineOfBusiness.all.each do |lob| # <-- this is Nil so fails
line_items.new(line_of_business_id: lob.id)
end
end
Any ideas why its Nil in the callback method?
line_items.count will fire query to database, and as you are not saving line_items in after_initialize callback, spec will fail. Instead try using line_items.size.
expect(item.line_items.size).to eq(lob_count)
Afternoon All,
I've just starting learning the testing side of rails and having some trouble with this below.
Let me quickly explain what I'm trying to test. If a user creates 10 approved snippets their rank should == "Author". A snippet is created on a book which I have listed in the factories but my code is all messed up and I'm not sure how I would write this.
Here is the code I've been playing with for the testing:
describe "ranking" do
let!(:book) { Book.create }
let!(:snippet) { FactoryGirl.create(:snippet1, :book1 => book) }
context "should be author after 10 approved posts" do
10.times do
FactoryGirl.create(:snippet1)
end
user.user_rank.should == "Author"
end
end
Here are my factories:
FactoryGirl.define do
factory :admin2, class: User do
first_name "admin1"
last_name "minstrator"
password "admin1234"
profile_name "profilename"
email "admin1#admin.com"
password_confirmation "admin1234"
admin true
end
factory :user2, class: User do
first_name "user2"
last_name "man2"
password "user1234"
profile_name "profilename"
email "user2#user.com"
password_confirmation "user1234"
admin false
end
factory :book1, class: Book do
title "Book1"
approved true
size 0
end
factory :snippet1, class: Snippet do
content "Snippet1"
approved true
end
end
EDIT: Error and related code:
app/models/snippet.rb:32:in `size_limit': undefined method `size' for nil:NilClass (NoMethodError)
This relates to a validation in the model shown below:
BOOK_SIZE = {
0 => {'per' => 500, 'total' => 15000},
1 => {'per' => 700 , 'total' => 30000},
2 => {'per' => 1000, 'total' => 50000}
}
def size_limit
book_limit = self.book.size.to_i
word_count = self.content.scan(/\w+/).size.to_i
current_snippets_size = (self.book.get_word_count || 0) + word_count
errors.add(:base, "Content size is too big") unless word_count < BOOK_SIZE[book_limit]['per'] && current_snippets_size < BOOK_SIZE[book_limit]['total']
end
I think your snippet1 factory should have user_id or something like that. now you create 10 snippet without association
edit: now I read your edit. but hire is almost the same. your snippet1 factory haven't any book so error on this
self.book.size.to_i
Your let(:snippet) clause uses a :book1 attribute, but the code is checking the self.book – could that be it?
Either way, the snippet.rb excerpt you've listed has two references to .size - without knowing your line numbers, it's hard for us to tell which one is throwing the error.
So either the book attribute or the content attribute is returning nil – and so when you call size on that nil attribute you get the error that's happening.
If snippet is only valid with a book reference and a non-nil content, add validations for those conditions. If there are situations where those attributes could be nil, make sure that your code makes allowances for that.
models/message.rb
class Message
attr_reader :bundle_id, :order_id, :order_number, :event
def initialize(message)
hash = message
#bundle_id = hash[:payload][:bundle_id]
#order_id = hash[:payload][:order_id]
#order_number = hash[:payload][:order_number]
#event = hash[:concern]
end
end
spec/models/message_spec.rb
require 'spec_helper'
describe Message do
it 'should save the payload' do
payload = {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"}
message = FactoryGirl.build(:message, {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"})
message.event.should == "order_create"
end
end
error_log
Failures:
1) Message should save the payload
Failure/Error: message = FactoryGirl.build(:message, {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"})
ArgumentError:
wrong number of arguments (0 for 1)
# ./app/models/message.rb:4:in `initialize'
# ./spec/models/message_spec.rb:7:in `block (2 levels) in <top (required)>'
FactoryGirl requires you to define factory first. Let's say in a file spec/factories/messages.rb:
FactoryGirl.define do
factory :message do
bundle_id 1
order_id 2
...etc...
end
end
After this you'll be able to invoke factory build create like this:
FactoryGirl.build(:message) # => bundle_id == 1, order_id == 2
FactoryGirl.build(:message, order_id: 3) # => bundle_id == 1, order_id == 3
However, there is one problem in your particular case. FactoryGirl's default builders operate on top of ActiveRecord-alike interface. It sets defined attributes through setters, not through a hash of attrs passed to the model constructor:
m = Message.new
m.bundle_id = 1
m.order_id = 2
So you have to create a custom constructor to work with the interface of your model (which doesn't conform to ActiveRecord-alike model) and register it in your factory definition. See factory girl docs for details.
Let me show you an example of doing so. Sorry I didn't test it but it should give you a clue:
FactoryGirl.define do
factory :message do
ignore do
# need to make all attributes transient to avoid FactoryGirl calling setters after object initialization
bundle_id 1
order_id 2
end
initialize_with do
new(payload: attributes)
end
end
end
It's because you have a constructor with mandatory arguments. You have a few options;
1) Make the argument non-mandatory (although this would mean you're changing your code to suit your tests - naughty!)
def initialize(message = nil)
2) Use the "initialize_with" syntax in your factory;
describe Message do
it 'should save the payload' do
payload = {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"}
message = FactoryGirl.build(:message, {:payload=>{:order_id=>138251, :order_number=>"AW116554416"}, :concern=>"order_create"})
message.event.should == "order_create"
end
initialize_with { new(message) }
end
In a RSpec spec file I have the following test
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.find(:all,
:select => ["*,(abs(rating - current_user.rating)) as player_rating"],
:order => "player_rating",
:limit => 5)
# test that matched_players array returns what it is suppose to
end
How would I complete this to test that matched_players is returning the correct users.
I think you should first introduce some test users to the test DB (using for example a Factory) and afterwards see that the test is returning the correct ones.
Also it would make more sense to have a method in your model that would return the matched users.
For example:
describe "Player matching" do
before(:each) do
#user1 = FactoryGirl.create(:user, :rating => 5)
...
#user7 = FactoryGirl.create(:user, :rating => 3)
end
it 'should return 5 players with ratings closest to the current_users rating' do
matched_players = User.matched_players
matched_players.should eql [#user1,#user3,#user4,#user5,#user6]
end
end
Your model shouldn't know about your current user (the controllers know about this concept)
You need to extract this as a method on the User class otherwise there's no point in testing it, i.e. why test logic that isn't even in your app code?
The function that gets the matched players doesn't need to know about the current user, or any user for that matter, just the rating.
To test it, create a bunch of User instances, call the method, and see that the result is a list of the correct user instances you expect.
models/user.rb
class User < ActiveRecord::Base
...
def self.matched_players(current_user_rating)
find(:all,
select: ["*,(abs(rating - #{current_user_rating)) as match_strength"],
order: "match_strength",
limit: 5)
end
...
end
spec/models/user_spec.rb
describe User do
...
describe "::matched_players" do
context "when there are at least 5 users" do
before do
10.times.each do |n|
instance_variable_set "#user#{n}", User.create(rating: n)
end
end
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4.2)
matched_players.should == [#user4, #user5, #user3, #user6, #user2]
end
context "when multiple players have ratings close to the given rating and are equidistant" do
# we don't care how 'ties' are broken
it "returns 5 users whose ratings are closest to the given rating, ordered by closeness" do
matched_players = described_class.matched_players(4)
matched_players[0].should == #user4
matched_players[1,2].should =~ [#user5, #user3]
matched_players[3,4].should =~ [#user6, #user2]
end
end
end
context "when there are fewer than 5 players in total" do
...
end
...
end
...
end
There are (at least?) two ways to use a sequence in factory girl:
Factory.sequence :my_id do |n|
"#{n}"
end
Factory.define :my_object do |mo|
mo.id Factory.next :my_id
end
and simply doing it inline:
Factory.define :my_object do |mo|
mo.sequence(:id) { |n| "#{n}" }
end
My question is this. If I use the inline version in two different factories, will there be two different sequences that both start at 1 and increment in tandem...meaning that if I create one of each type of factory object they will both have id 1?
If I use the externally defined sequence in two different factories am I guaranteed to get unique ids across the two objects? Meaning will the ids of each object be different?
I am trying to confirm if the behavior above is accurate because I'm working with a completely goofy data model trying to get rspec & factory girl to play nice with it. The designer of the database set things up so that different objects have to have ids generated that are unique across a set of unrelated objects. Changing the data model at this point is not a feasible solution though I'd really love to drag this stuff back onto the Rails.
When using externally defined sequences in two different factories you will see incrementing ids across the factories. However, when using inline sequences each factory will have their own sequence.
I created the example rake task below to illustrate this. It displays the following results:
*** External FactoryGirl Sequence Test Results ***
User Name: Name 1
User Name: Name 2
User Name: Name 3
User Name: Name 4
Role: Name 5
Role: Name 6
Role: Name 7
Role: Name 8
*** Internal FactoryGirl Sequence Test Results ***
User Name: Name 1
User Name: Name 2
User Name: Name 3
User Name: Name 4
Role: Role 1
Role: Role 2
Role: Role 3
Role: Role 4
As you can see, when using external sequences the number continues to increase as you move from the user to the role. However when using an inline sequence the increments are independent of each other.
The following schema files were used for this example:
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
end
create_table "roles", :force => true do |t|
t.string "name"
end
The example rake task is:
require 'factory_girl_rails'
namespace :sequencetests do
Rake::Task[:environment].invoke
task :external do
FactoryGirl.factories.clear
desc "Factory Girl Sequence Test using an externally defined sequence"
puts "*** External FactoryGirl Sequence Test Results ***"
FactoryGirl.define do
sequence :name do |n|
"Name #{n}"
end
factory :user do |u|
name
end
factory :role do |r|
name
end
end
users = buildit(:user)
roles = buildit(:role)
puts( showit(users, "User Name: "))
puts( showit(roles, "Role: "))
end
task :inline do
FactoryGirl.factories.clear
puts "*** Internal FactoryGirl Sequence Test Results ***"
desc "Factory Girl Sequence Test using an inline sequence"
FactoryGirl.define do
factory :user do |u|
u.sequence(:name) {|n| "Name #{n}" }
end
factory :role do |r|
r.sequence(:name) {|n| "Role #{n}" }
end
end
users = buildit(:user)
roles = buildit(:role)
puts( showit(users, "User Name: "))
puts( showit(roles, "Role: "))
end
end
task sequencetests: ['sequencetests:external', 'sequencetests:inline']
def buildit(what)
items = []
4.times do
items << FactoryGirl.build(what)
end
items
end
def showit(items, prefix = "Name: ")
results = ""
items.each do |item|
results += "#{prefix}#{item.name}\n"
end
results
end
I hope this helps explain the different possibilities when using sequences in FactoryGirl.
Yes, the inline versions will create 2 independent sequences, each starting at 1