I just figured out how to display a country name from a country id number on model User. Here is the basic code I am using in a controller and two views to display the name after finding User by its id:
#country_name = Country.find(#user.country_id).name
I am using Factory Girl to simulate user records where the default for country_id is 1 for the United States. Before I added this logic my Rspec2 tests were clean. Now I get the following error when I run my tests. Every test which has similar logic in the view or controller produces the error.
ActiveRecord::RecordNotFound:
Couldn't find Country with id=1
Here are two of the Rspec2 tests I am doing:
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_selector('span', text: user.first_name+' '+user.last_name) }
it { should have_selector('title', text: user.first_name+' '+user.last_name) }
end
The profile page references #country_name which is set as stated above. The country name displays on the screen as expected.
I'm thinking that I need to add something to the Rspec2 tests somehow. I was not sure where to do this. Since #country_name is an instance variable not related to User I felt that maybe I needed to do something directly in my Rspec2 file.
Any help would be appreciated. I have not found anything like this so far but I will continue looking.
Update 6/8/2012 7:27 pm CDT
Here is my user controller logic. Adding the second line here produced the RSpec2 errors. I changed nothing in the RSpec2 tests. #country_name is referenced in show.html.erb.
def show
#user = User.find(params[:id])
#country_name = Country.find(#user.country_id).name
end
I decided to try this but got the same error:
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
let(:country) { FactoryGirl.create(:country) }
before { visit user_path(user) }
it { should have_selector('span', text: user.first_name+' '+user.last_name) }
it { should have_selector('title', text: user.first_name+' '+user.last_name) }
end
I added the following to factories.rb separate from the user factory.
factory :country do
id 1
name "CountryName"
iso "CN"
end
Again my application logic for the country name is working wonderfully.
Update 6/11/2012 9:09 am CDT
Here is my factories.db file. country_id has a default value of 1 in User. I tried this with country_id defined as below and without a declaration for country_id. I also had the country factory without the id. I still got the errors as described. I tried this before submitting my question here.
FactoryGirl.define do
factory :user do
sequence(:first_name) { |n| "First Name #{n}" }
sequence(:last_name) { |n| "Last Name #{n}" }
sequence(:username) { |n| "Username#{n}" }
sequence(:email) { |n| "person_#{n}#example.com" }
password "foobar"
password_confirmation "foobar"
sequence(:city) { |n| "City #{n}" }
sequence(:state_province) { |n| "State/Province #{n}" }
active_user "1"
country_id 1
factory :admin do
admin true
active_user "3"
end
factory :blocked do
active_user "1"
end
factory :rejected do
active_user "2"
end
factory :not_visible do
active_user "3"
visible false
end
factory :visible do
active_user "3"
visible true
end
end
factory :country do
id 1
name "CountryName"
iso "CN"
end
end
Update 6/11/2012 5:37 pm CDT
After several hours of continuing to search AND COMPLETELY REBOOTING MY COMPUTER (sigh) I was finally able to get the tests to work. I replaced country with user.association :country in the user factory. I put my original two statements after the describe "profile page" do statement back. With the suggested changes I now have no errors. I guess the association will create corresponding rows in Country.
Thanks so much for all the help. It was a combination of the help received with one modification that solved this one.
On to figuring out how to check for changed values.......
Update 6/12/2012 10:55 am
It looks like with all the correcting of code I am able to use #user.country.name with no errors. I will change my code to use this streamline coding.
Let is more for setting up variables for use in the test, if you're using database cleaner or something similar, these are probably getting destroyed in the database on each test run. You should be setting up your factories in the before block:
Edit: I've just realised this is probably because you're trying to match records by explicitly entering ids. This won't work (ids are automatically set by the database), but Factory Girl should deal with your associations, see what happens when you set up the before block like this:
before(:each) do
country = FactoryGirl.create(:country)
user = FactoryGirl.create(:user, country: country)
visit user_path(user)
end
I would also remove the
id 1
line from your country factory, and the line setting up the country_id to 1 on the user factory.
Edit 2: You need to tell Factory Girl about your associations, don't try to set them up by explicitely setting the id field because that won't work.
Remove
country_id 1
from the user factory
id 1
from the country factory, and add this line to the user factory:
country
(yes, just country on it's own - Factory Girl is clever enough to figure out what's going on from that as long as the factory and association is named the same)
By the way, you could change this:
#country_name = Country.find(#user.country_id).name
to
#country_name = #user.country.name
It won't help here, but it's cleaner.
Related
I have 2 models which I want to test: Article and User. User has_many articles. In one of the test cases I define a variable:
context 'some context' do
let(:my_user) { FactoryGirl.create(:user) }
How do I create 100 articles belonging to that my_user? I tried this:
let(:my_user) { FactoryGirl.create(:user, articles: [build(:article)]) } # error
# or
let(:article) { FactoryGirl.create(:article, user: my_user) } # doesn't work
it 'fdsfdsfs' do
# my_user.articles still has no articles
end
but it didn't pan out.
You can either do it in the factory itself using callbacks or you can do it using create_list like so:
let!(:user) { FactoryGirl.create(:user) }
let!(:article) { FactoryGirl.create_list(:article, 100, user: user) }
I'm using let! here which might not be necessary but because you're not showing your entire test I can't be sure if everything is being lazy loaded.
To do it with callbacks, I like to do something like this:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#email.com" }
password 'password'
factory :user_with_articles do
after(:create) do |user|
create_list(:article, 100, user: user)
end
end
end
end
Now in your test you can do
let(:user) { FactoryGirl.create(:user_with_articles) }
This will create a user with 100 articles.
As a complement to the fine answer https://stackoverflow.com/a/27756067/1008891, it appears that your first attempt failed because you tried to invoke FactoryGirl.build without qualifying the reference to build and your second attempt failed because let(:article) was lazily evaluated and you never referenced article in your test, resulting the article never getting associated with your user.
Here's my integration spec:
let(:user){ FactoryGirl.build :user }
subject { page }
feature 'authentication' do
describe 'guest signs up' do
before do
sign_up user
end
it { should have_title user.user_name }
it { should have_link user.user_name, href: user_path(user.id) }
end
end
The spec it { should have_link user.user_name, href: user_path(user.id) } fails. Why? The id is wrong.
I've built the factory in memory, so it has no id so the spec fails. However, if I give it an id like this:
factory :user do
password = Faker::Internet.password
id 3
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
user_name { Faker::Internet.user_name }
password { password }
password_confirmation { password }
email { Faker::Internet.email }
uuid { SecureRandom.uuid }
end
The spec still fails. It's actual id is 10. This 10 is created by active record I think, and overrides my desire ot have an id of 3.
How do I get around this, forcing the id?
Should I be forcing the id? How should I be testing when user is a built user?
You can't usefully force the id: the whole point of your spec is to test that a user is created by your controller code (since you're writing an integration spec you probably don't want to stub out the actual saving of the record).
Your controller code won't let you force the id of the created record, (and in the real world you wouldn't want to let users do this), so the setup of your factory doesn't matter.
Since you want to test is that the page contains a link to the show page for the freshly created user, all you need is the id of that user: instead of using the id from the factory built object, use User.last.id
It might be an idea to also spec that a new user was actually created (i.e that User.count changed)
Try using FactoryGirl.create instead of build.
Long time reader but first time poster here on SO :)
For the last couple of days I've been setting up FactoryGirl.
Yesterday I changed some factories (mainly my User and Brand factories) by replacing:
Language.find_or_create_by_code('en')
With:
Language.find_by_code('en') || create(:language)
Because the first option creates a Language object with only the code attribute filled in; while the second uses the Language factory to create the object (and thus fills in all the attributes specified in the factory)
Now when I run my test it immediately fails on Factory.lint, stating my user (and admin_user) factories are invalid. Reverting the above code doesn't fix this and the stack trace provided by FactoryGirl.lint is pretty useless..
When I comment the lint function, my tests actually run fine without any issues.
When I manually create the factory in rails console and use .valid? on it, it returns true so I'm at a loss why lint considers my factories invalid.
My user factory looks like this:
FactoryGirl.define do
factory :user do
ignore do
lang { Language.find_by_code('en') || create(:language) }
end
login "test_user"
email "test_user#test.com"
name "Jan"
password "test1234"
password_confirmation "test1234"
role # belongs_to :role
brand # belongs_to :brand
person # belongs_to :person
language { lang } # belongs_to :language
factory :admin_user do
association :role, factory: :admin
end
end
end
Here the role, person and language factories are pretty straightforward (just some strings) but the brand factory shares the same language as the user thus I use the code in the ignore block so FactoryGirl doesn't create 2 'en' language entries in my database.
Anyone has some ideas why I'm getting this InvalidFactoryError and maybe provide some insights on how to debug this?
UPDATE 1
It seems this problem is caused by another factory..
I have a factory called user_var_widget where I link a specific widget with a user:
factory :user_solar_widget, :class => 'UserWidget' do
sequence_number 2
user { User.find_by_login('test_user') } # || create(:user) }
widget { Widget.find_by_type('SolarWidget') || create(:solar_widget) }
end
If I uncomment the create(:user) part, I get InvalidFactoryError for the User factory. My guess is because there is nothing in the User factory that states it has any user_widgets. I will experiment a bit with callbacks to see if I can resolve this.
UPDATE 2
I've managed to solve this by adding this to my User factory:
trait :with_widgets do
after(:create) do |user|
user.user_widgets << create(:user_solar_widget, user: user)
end
end
Where user_widgets is a has_many association in the user model.
Then I changed my user_solar_widget factory to:
factory :user_solar_widget, :class => 'UserWidget' do
sequence_number 2
# removed the user line
widget { Widget.find_by_type('SolarWidget') || create(:solar_widget) }
end
I then create a user by calling:
create :user, :with_widgets
Still, it would have been nice if the lint function was a bit more specific about invalid factories..
FactoryGirl.lint is almost useless because of it's non-existent error messages. I recommend instead including the following test:
# Based on https://github.com/thoughtbot/factory_girl/
# wiki/Testing-all-Factories-(with-RSpec)
require 'rails_helper'
RSpec.describe FactoryGirl do
described_class.factories.map(&:name).each do |factory_name|
describe "#{factory_name} factory" do
it 'is valid' do
factory = described_class.build(factory_name)
expect(factory)
.to be_valid, -> { factory.errors.full_messages.join("\n") }
end
end
end
end
Like suggested from the title, I've a strange mistake. In fact when i try to make an operation reserved to the test enviroment (for example: rake test:prepare), in some way it influence the other environments and delete all the record in my database (mysql).
(I'm using rspec, and i'm following the tutorials from Michael Hartl.)
Another example (maybe clearer) is when I write the tests that implicate the creation and the cancellation of new users throught FactoryGirl:
Location: spec/factories.rb
FactoryGirl.define do
factory :user do
sequence(:name) {|n| "Person #{n}"}
sequence(:email) {|n| "person_#{n}#example.it"}
password "luckyluke"
password_confirmation "luckyluke"
factory :admin do
admin true
end
end
Location: spec/requests/users_pages_spec.rb
describe "pagination" do
before(:all) { 30.times { FactoryGirl.create(:user) } }
after(:all) { User.delete_all }
# tests...
end
Although I'm into the test enviroment, when i run
bundle exec rspec spec/requests/users_pages_spec.rb , rails really create 30 users on my database, and ater really delete ALL the users from database. (Also the users created and stored before the test!!).
P.S. sorry for my english
Mocking the database should happen before each test, to ensure isolation. This way, FactoryGirl is also responsible for deleting all its created (as is described in this answer):
describe "pagination" do
before(:each) { 30.times { FactoryGirl.create(:user) } }
# tests...
end
Also, it is advisable that the database will not contain any data from before the test, since it might change the behavior of the tests - it should contain only what the test expects it to contain.
I'm trying to get a simple test up and running with rspec + fabrication. Unfortunately, not too many decent articles on this.
In spec/model/event_spec.rb
require 'spec_helper'
describe Event do
subject { Fabricate(:event) }
describe "#full_name" do
its(:city) { should == "LA" }
end
end
In spec/fabricators/event_fabricator.rb
Fabricator(:event) do
user { Fabricate(:user) }
# The test works if I uncomment this line:
# user_id 1
city "LA"
description "Best event evar"
end
In spec/fabricators/user_fabricator.rb
Fabricator(:user) do
name 'Foobar'
email { Faker::Internet.email }
end
I keep getting:
1) Event#full_name city
Failure/Error: subject { Fabricate(:event) }
ActiveRecord::RecordInvalid:
Validation failed: User can't be blank
PS if anyone knows of any online articles / tutorials worth a read on getting started with rspec and fabrication. Do let me know
One of the features of Fabricator is that it lazily generates associations, meaning that your User won't get generated unless the user accessor is called on the Event model.
It looks like your Event model has a validation which requires a User to be present. If this is the case, you need to declare your fabricator like this:
Fabricator(:event) do
# This forces the association to be created
user!
city "LA"
description "Best event evar"
end
This ensures that the User model is created along with the Event, which will allow your validations to pass.
see: http://fabricationgem.org/#!defining-fabricators