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
Related
I'm trying to test my User model's class method #registered_but_not_logged_in(email), which grabs the first user that matches the email that has a confirmed_at entry but has never logged in (which I'm counting with sign_in_count). I'm using rspec with Factorygirl, plus shoulda-matchers v2.8.
Here's the ruby:
def self.registered_but_not_logged_in email
self.with_email( email ).confirmed.never_signed_in.first
end
I've tested this in the Rails console and I know it works as expected so it's not a logic problem on that end, so I'm thinking I'm doing something wrong in my test:
describe User do
# create #user
describe ".registered_but_not_logged_in" do
it "returns a user that matches the provided email who is confirmed, but who has not yet signed in" do
#user.confirmed_at = 2.days.ago
#user.email = "fisterroboto5893#mailinator.com"
result = described_class.registered_but_not_logged_in("fisterroboto5893#mailinator.com")
expect(result).to be_instance_of(User)
end
end
In this example, result is nil. I know that this is a case of #user existing outside the database while the method is actively checking the DB, but I don't know how to handle this while using rspec/factorygirl. Any help is definitely appreciated!
So I'm not 100% sure why what I'm doing is working, but here's the solution that I stumbled across with the help of #nort and one of my coworkers:
it "returns a user that matches the provided email who is confirmed, but who has not yet signed in" do
#user.confirmed_at = 2.days.ago
#user.email = "fisterroboto5893#mailinator.com"
#user.sign_in_count = 0
#user.save!
expect(User.registered_but_not_logged("fisterroboto5893#mailinator.com")).to be_instance_of(User)
end
What I believe is happening is the save! is saving #user to the test database, which is otherwise completely unpopulated as I develop against a different DB. The issue of course being that we can't test data that doesn't exist.
As a bonus, note that expect().to... is the preferred convention for rpsec. Also, described_class I believe would totally work fine, but am preferring explicitness right now since I'm still learning this stuff.
I have a User class with a save method which makes a change to one of the user instance attributes. Specifically, each user has an options hash that gets one of its values deleted during the save process.
I have an rspec test with 2 context groups. Each group creates a new #user object using FactoryGirl.build(:user). When I call #user.save in the first context group, the attribute change occurs as expected. However, the second time that FactoryGirl.build(:user) gets called, it doesn't return a User object according to the FactoryGirl definition. It returns a user object with an options hash that is missing the same value that gets deleted during the save process. This object is not valid, and as a result #user.save fails the second time.
UPDATE: I tried changing the variable names and I still have the same problem. The issue seems to be with the FactoryGirl :user factory being modified somehow during the first example, resulting in the second example failing.
Below is a simplified version of my code. Whichever context group is executed second ("with avatar" or "without avatar") when run randomly by Rspec is the one that fails. I have used puts in both cases to confirm that the second #user has a bad options hash, and causes the test to fail.
describe "save" do
context "with avatar" do
before(:context) do
#user = FactoryGirl.build(:user)
puts #user
#save_result = #user.save
end
after(:context) do
delete_user(#user)
end
it "should return true" do
expect(#save_result).to be true
end
end
context "without avatar" do
before(:context) do
#user = FactoryGirl.build(:user, avatar: nil)
puts #user
#save_result = #user.save
end
after(:context) do
delete_user(#user)
end
it "should return true" do
expect(#save_result).to be true
end
end
end
I suspect that the options hash gets reused.
According to the FactoryGirl readme, when you want to add a hash attribute to a FactoryGirl definition and that hash is dynamic (i.e. not the same among all created instances), you need to wrap it in a block:
Instead of:
factory :user do
options { option1: 1, option2: 2 }
end
You need to do:
factory :user do
options { { option1: 1, option2: 2 } }
end
A user has many comments, so I would like to have a factory user with a comment associated to it (user_with_comment):
factory :user, class: User do |t|
...
factory :user_with_comment do |t|
after(:create) do |user|
FactoryGirl.create(:comment, user_id: user.id)
end
end
It works fine... when I call FactoryGirl.create(:user_with_comment), it creates the user and the related comment in my test db.
However, I'm facing some issues in the controller_spec:
Using let I have to reload the user to see the comment:
let(:user) { FactoryGirl.create(:user_with_comment) }
user.comments.size #=>0
user.reload
user.comments.size #=>1
One solution would be using before(:each), but it will create venda and comment before each test:
before(:each) do
#user = FactoryGirl.create(:user_with_comment)
end
#user.comments.size #=>1
Or, I can reload the userbefore each test, but it will also hit the database:
let(:user) { FactoryGirl.create(:user_with_comment) }
before(:each) do
user.reload
end
What is the best approach in this situation?
This has to do with the fact that let is lazy-evaluated and the comment for your user factory is added after the user is created.
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked.
See the RSpec documentation on let and let!
Using let means the user created when you first use it in an example and thus the comment is created after that. You can use let! to create the user before each example to avoid the reload. This shouldn't negatively impact your tests if you're using the factory in all/most of the examples in the spec.
How about this?
let(:user) { FactoryGirl.create(:user_with_comment).reload }
I'm new to Rspec and I'm putting in tests for an old project. I'm having issues with setting variables. Bascially my User object is affected by actions in previous tests.
I have a simple user factory:
FactoryGirl.define do
factory :user do
first_name 'Bob'
last_name 'Shabbadoo'
office
login 'lurker48'
email 'test.user#test.com'
password 'TestWhatNots#21'
password_confirmation{|u| u.password}
end
end
Then I have my actual rspec tests.
require "spec_helper"
describe User do
it 'has a valid factory' do
FactoryGirl.build(:user).should be_valid
end
describe '#password' do
it "cannot contain the word 'password'" do
valid_user = FactoryGirl.build(:user)
valid_user.password << "password"
valid_user.password_confirmation = valid_user.password
valid_user.should_not be_valid
end
it "cannot contain the users last_name" do
valid_user = FactoryGirl.build(:user)
valid_user.password << valid_user.last_name
valid_user.password_confirmation = valid_user.password
valid_user.should_not be_valid
end
it "cannot contain the users first_name" do
valid_user = FactoryGirl.build(:user)
valid_user.password << valid_user.first_name
valid_user.password_confirmation = valid_user.password
valid_user.should be_valid
end
end
end
I purposefully made the "cannot contain the users first_name" test fail
and as I expected I got this:
User
has a valid factory
#password
cannot contain the word 'password'
cannot contain the users last_name
cannot contain the users first_name (FAILED - 1)
But when I took a closer look a the password it looked like this:
TestWhatNots#21passwordShabbadooBob
Why would actions in previous tests taint the information?
When you build new user object, factory girl assigns to its password given string (it is, to the instance of String class). Every time you do this, you user password is pointing to that object - if you build two users, their password are not only identical - the are the same object.
By using << method youre altering this object and hence you are altering password value for all objects which have been or will be created by this factory.
u1 = FactoryGirl.build(:user)
u2 = FactoryGirl.build(:user)
u1.password.object_id == u2.password.object_id #=> true!
Solutions:
Option 1. Do not modify the object but assign a new string object instead:
valid_user.password += valid_user.first_name
Option 2. Wrap all your values within a block. This will force FactoryGirl to run block each time you build new object and hence to create separate but identical String instances:
factory :user do
first_name { 'Bob' }
last_name { 'Shabbadoo' }
...
I've put together a basic application with user authentication using bcrypt-ruby and has_secure_password. The result is essentially a barebones version of the application from the Rails Tutorial. In other words, I have a RESTful user model as well as sign-in and sign-out functionality.
As part of the tests for editing a user's information, I've written a test for changing a password. Whereas changing the password works just fine in the browser, my test below is not passing.
subject { page }
describe "successful password change"
let(:new_password) { "foobaz" }
before do
fill_in "Password", with: new_password
fill_in "Password Confirmation", with: new_password
click_button "Save changes"
end
specify { user.reload.password.should == new_password }
end
Clearly, I'm misunderstanding some basic detail here.
In short:
1) Why exactly is the code above not working? The change-password functionality works in the browser. Meanwhile, rspec continues to reload the old password in the last line above. And then the test fails.
2) What is the better way to test the password change?
Edit:
With the initial password set to foobar, the error message is:
Failure/Error: specify { user.reload.password.should == new_password }
expected: "foobaz"
got: "foobar" (using ==)
Basically, it looks like the before block is not actually saving the new password.
For reference, the related controller action is as follows:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile Updated"
sign_in #user
redirect_to root_path
else
render 'edit'
end
end
For Devise users, use #valid_password? instead:
expect(user.valid_password?('correct_password')).to be(true)
Credit: Ryan Bigg
One not so satisfying solution here is to write a test using the #authenticate method provided by bcrypt-ruby.
specify { user.reload.authenticate(new_password).should be_true }
Granted this isn't a proper integration test, but it will get us to green.
Your answer (using authenticate) is the right approach; you should be satisfied with it. You want to compare the hashed versions of the passwords not the #password (via attr_accessor) in the model. Remember that you're saving a hash and not the actual password.
Your user in your test is an copy of that user in memory. When you run the tests the update method loads a different copy of that user in memory and updates its password hash which is saved to the db. Your copy is unchanged; which is why you thought to reload to get the updated data from the database.
The password field isn't stored in the db, it's stored as a hash instead, so the new hash gets reloaded from the db, but you were comparing the ephemeral state of #password in your user instance instead of the the encrypted_password.