Force saving of id with Factory Girl - ruby-on-rails

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.

Related

Why my factory test raises an error from another factory?

I'm quite new with Rspec and factory_bot. I have the following factories:
FactoryBot.define do
factory :user do
email { "test#gmail.com" }
password { "azerty" }
username { "test" }
city { "Paris"}
end
factory :game do
title { "Mega Man X" }
nostalgia_point { 9 }
platform { "Super Nintendo" }
developer { "Capcom" }
release_date { Date.new(1994,1,1) }
game_mode { "Single player" }
genre { ["Adventure", "Platform", "Shooter"] }
association :owner, factory: :user
end
factory :favorite do
user
game
end
end
When I run this test:
require 'rails_helper'
RSpec.describe Game, type: :model do
let!(:favorite) { create(:favorite) }
describe '::create' do
context 'persistence' do
it 'persists the favorite' do
expect(Favorite.count).to eq(1)
end
end
end
end
I get the following error: ActiveRecord::RecordInvalid: Validation failed: Email has already been taken, Username has already been taken
I guess it's because my user factory (devise model) is not cleaned from my other tests but I'm absolutely not sure... And I don't know if I should use something different from what I did to clean all my database. Here what I wrote in factory_bot.rb:
RSpec.configure do |config|
config.include Warden::Test::Helpers
config.after :each do
Warden.test_reset!
end
config.include FactoryBot::Syntax::Methods
end
If there is a better practice or just tell me what I'm missing, I'll be very grateful. Thanks!
There must be a validation in your user model that prevents you from creating users with an email and/or username that's already in the database. And as you have a factory (favorite), that attempts to create a user and a game, without checking first if the user email and/or username already exist, it fails because it uses the values you set for them, which are always the same ("test#gmail.com", "test", correspondingly).
You can use a FactoryBot sequence for that:
sequence :email do |n|
"test#{n}#gmail.com"
end
sequence :username do |n|
"test-#{n}"
end
That prevents you from using the same value for new records as sequence yields an incremental integer, hence the value is guaranteed to not be the same as long as the test suite runs.

How to share a variable with many "it" examples in rspec

I am using let to create a user record using factory girl. However i want to use exactly the same variable across 2 tests in the context as the user_id and email are important to the external API i am sending.
However i had no luck making a single variable for using across the examples. Here is my current code
context "User" do
let(:user) { FactoryGirl.create(:user) }
it "should create user and return 'nil'" do
expect(send_preferences(user, "new")).to eq nil
end
it "should not create user preferences again after sending two consecutive same requests" do
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
expect(send_preferences(user, "update")).to eq nil
end
end
any clues?
You can use lets within lets:
context "User" do
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
You will then also have access to the email_address variable within all your tests.
This works because previously the email address was being randomly generated by the factory every time the user was created, as we hadn't set a value for it anywhere. So, we called the code below in each test:
send_preferences(user, "new")
It called the 'user' let which created a new user with a completely random email address (as we hadn't give it a specific email value). Therefore during the backend API call it was sending a different email address every time.
let(:user) { FactoryGirl.create(:user) }
However, when we defined the email address 'let' as 'test#test.com', and passed that into the user factory as in the code I provided, we overrode the randomly generated email address with our own static value, So, every time we call the code again:
send_preferences(user, "new")
It now triggers the user factory create which is also taking our new 'email_address' let, which is always set to a specific value of test#test.com every time it is called.
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
Therefore, when the backend API call is made the email address is always what we set it to.
Also, as it is a let we can use that variable in any of the tests themselves if we wish. For example:
it 'should set the email address' do
expect(user.email_address).to eq(email_address)
end
It's quite hard to explain in a few sentences but let me know if that's still not clear.
Having an instantiated variable shared among multiple tests is an anti-pattern 90% of the time in my opinion.
The problem with doing something like the below is you will be creating objects in your db without doing a cleanup.
before(:all) do
#user = FactoryGirl.create :user
end
Sure, you can do a before(:after) block or use DatabaseCleaner, but I think it is much better practice for tests to be as standalone as possible. In your case, make your setup of a send_preferences event before making an expectation on what happens the second time:
context "User" do
let(:user) { FactoryGirl.create(:user) }
# ...
it "should not create user preferences again after sending two consecutive same requests" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "update")).to eq nil
end
end

Defining a "child" variables in "let"

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.

Getting Rspec2 Errors On Working Rails Code

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.

How to get a simple test to run with rspec + fabrication?

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

Resources