FactoryGirl and Rspec - ruby-on-rails

I've very green to this TDD business, so any help would be fantastic!
So, I've got a factory with the following:
FactoryGirl.define do
factory :account do
email "example#example.com"
url "teststore"
end
end
And an Rspec test with:
it "fails validation without unique email" do
account1 = FactoryGirl.create(:account)
account2 = FactoryGirl.create(:account)
account2.should have(1).error_on(:email)
end
I get a failure with the following message:
1) Account fails validation without unique email
Failure/Error: account2 = FactoryGirl.create(:account)
ActiveRecord::RecordInvalid:
Validation failed: Email taken, please choose another, Url taken, please choose another
# ./spec/models/account_spec.rb:11:in `block (2 levels) in <top (required)>'
Is this the correct way to create new factories? Any ideas what I'm doing wrong here (I have no doubt I'm doing something totally incorrect!)
EDIT: I'm thinking of instead of using 'create' on the second account, I may want to use .build and then .save instead?

Save yourself the database interactions and use the build method for situations like this.
it "fails validation without unique email" do
account1 = create(:account)
account2 = build(:account)
account2.should_not be_valid
account2.should have(1).error_on(:email)
end
You don't need to try and create an account for valid? to return false. You have access to the errors object on the account even when it's just built in memory. This will decrease database interactions and thus making your tests much faster.
Have you considered using sequences in your factories? I don't know how far along you are with your RSpec / FactoryGirl experience, but you will find that things like the following are very useful.
factories.rb
factory :account do
sequence(:email) { |n| "user#{n}#example.com" }
url "teststore"
end
Every time you call build or create on the account factory, you will get unique emails.
Remember that you can always specify values for the attributes on the factory using the options hash. So when testing your uniqueness validation on the account, you would do something like this.
it "fails validation without unique email" do
account1 = create(:account, :email => "foo#bar.com")
account2 = build(:account, :email => "foo#bar.com")
account2.should_not be_valid
account2.should have(1).error_on(:email)
end

Try this:
FactoryGirl.create(:account)
lambda {
FactoryGirl.create(:account)
}.should raise_error(ActiveRecord::RecordInvalid)
This will help - with similar syntax to what you're doing.
However, searching for "rspec validate_uniqueness_of" will find you some more elegant approaches instead of using factory girl like this!

Related

How to test a class method in rspec that returns a model instance

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.

Testing Rails model validations with RSpec, without testing AR itself

Testing Rails model validations with RSpec, without testing AR itself
Lets as setup we have model User:
class User < ActiveRecord::Base
validate :name, presence: true, uniqueness: { case_sensitive: false }, on: :create
validate :password, presence: true, format: { with: /\A[a-zA-z]*\z/ }
end
A see several ways to test this:
it { expect(user).to validate_presence_of(:name).on(:create) }
or
it do
user = User.create(name: '')
expect(user.errors[:name]).to be_present
end
My main question is which of the approaches is better and why? Can suggest me different approach?
Additional questions:
How much should I test? As an example, I can write so many tests for the regex, but it will be hell for maintenance.
How much you think will be full test coverage in this example?
The functionalities of:
Rails being able to validate the presence of an arbitrary value on your model
errors being added to an object for an attribute that is missing when a validation for it is configured
are covered in the tests for Rails itself (specifically, in the ActiveModel tests).
That leaves needing to write the tests for the config that covers the business logic of your app eg validating the presence of the specific name attribute on your specific User class etc. In my opinion, the matchers from the shoulda-matchers gem should have you covered:
RSpec.describe User, type: :model do
subject(:user) { build(:user) } # assuming you're using FactoryGirl
describe 'validations' do
specify 'for name' do
expect(user).to validate_presence_of(:name).on(:create)
# NOTE: saving here is needed to test uniqueness amongst users in
# the database
user.save
expect(user).to validate_uniqueness_of(:name)
end
specify 'for password' do
expect(user).to validate_presence_of(:password)
expect(user).to allow_value('abcd').for(:password)
expect(user).to_not allow_value('1234').for(:password)
end
end
end
I think that unless you have specific custom error messages for your errors that you want to test for (ie you've overridden the default Rails ones), then tests like expect(user.errors[:name]).to be_present can be removed (even if you have custom errors, I still think they're of dubious value since those messages will become locale-dependent if you internationalise your app, so I'd test for the display of some kind of error on the page in a feature spec instead).
I can write so many tests for the regex, but it will be hell for maintenance.
I don't think you can really get around this when testing validations for format, so I'd suggest just write some representative test cases and then add/remove those cases as you discover any issues you may have missed, for example:
# use a `let` or extract out into a test helper method
let(:valid_passwords) do
['abcd', 'ABCD', 'AbCd'] # etc etc
end
describe 'validations' do
specify 'for password' do
valid_passwords.each do |password|
expect(user).to allow_value(password).for(:password)
end
end
end
How much you think will be full test coverage in this example?
I've gotten 100% code coverage from reports like SimpleCov when writing unit specs as described above.
These 2 of them should be used, because:
it { expect(user).to validate_presence_of(:name).on(:create) }
=> You are expecting the validate_presence_of should be run on create, this should be the test case for model
it do
user = User.create(name: '')
expect(user.errors[:name]).to be_present
end
=> You are expecting a side effect when creating user with your input, so this should be the test case for controller
Why you shouldn't remove 1 of them:
Remove the 1st test case: what happens if you do database validation level instead, you expect an active record level validation
Remove the 2nd test case: what happens on controller actually creates a new User, how do you expect the error returning!

RSpec test for not allowing two users with the same email address

First of all I should probably mention that I'm very new to Rails and this is my first "serious" project, so I apologise if this is a simple question but I can't find an answer for it.
I'm using TDD in my project and am using RSpec to write the model tests, FactoryGirl to create the models and Faker to create dummy data for the models. Everything has been going really well until I added a test to make sure no two users have the same email address. In my User model I validated it like so:
# /app/models/user.rb
validates :email, :password_reset_code, :auth_token, uniqueness: true
My factory creates a user model with Faker, like so:
# /spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password { Faker::Internet.password }
password_reset_code { Faker::Lorem.word }
auth_token { Faker::Lorem.word }
end
end
and my user_spec.rb test for this is as follows:
# /spec/models/user_spec.rb
it "is invalid with a duplicate email" do
user = FactoryGirl.create(:user)
FactoryGirl.create(:user, email: user.email).should_not be_valid
end
Here I'm creating a new model with FactoryGirl using its dummy values from Faker, saving it to the database and then creating another one with the same email as the first one. I'd expect RSpec to tell me this test passed because of the should_not be_valid part. But instead I get this output when I run the test:
Failures:
1) User is invalid with a duplicate email
Failure/Error: FactoryGirl.create(:user, email: user.email).should_not be_valid
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
# ./spec/models/user_spec.rb:19:in `block (2 levels) in <top (required)>'
So it seems that the model validation is raising an error which RSpec isn't catching and using to pass the test? I've managed to work around it by changing the test to this:
it "is invalid with a duplicate email" do
begin
user = FactoryGirl.create(:user)
FactoryGirl.create(:user, email: user.email).should_not be_valid
rescue
false
end
end
which seems to work, however I have a feeling this isn't the best way to do it.
What's the proper way to write this test?
I ran into this problem too. The error you're encountering is due to the fact that the create() method actually persists the model, which is then throwing an ActiveRecord::RecordInvalid at the DB layer (well, the persistence layer). If you want to assert if a model is valid or not you should use the build() method for your second object and then ask if it's valid. You can read up on it in this post.
Additionally, if you're just trying to test various validations on models and what not I wrote a quick and dirty gem that you can use to assert some of the more basic model validations. You can check it out here.
Hope that helps.
I would go with:
# /spec/models/user_spec.rb
describe 'validations' do
context 'with a duplicate email' do
let(:other_user) { FactoryGirl.create(:user) }
let(:attributes) { FactoryGirl.attributes_for(:user) }
subject(:user) { User.new(attributes.merge(email: user.email)) }
it 'is not valid' do
expect(user).to_not be_valid
end
end
end

Rspec Running Tests Results in Validation Errors

For example:
I run a test and all asserts pass, but I run the test again, and in my case, I receive the following error:
Validation failed: Email has already been taken
It seems adding: sequence(:email) {|n| "nobody#{n}#xyz.com" } for factorygirl is pointless
The tests seem to pass sometimes and others fail for errors reasons like these.
Please advise on the problem/solution.
try deleting all the records from tables before running test case.
eg:-
describe User do
before(:each) do
User.delete_all
end
it "validate e-mail" do
(do staff..)
end
end
I´m not sure it is a definitive solution, but i added random numbers to my product references on the factories with factorygirl using the lazy attributes.
Example:
FactoryGirl.define do
factory :product do
reference {"AB"+rand(999).to_s}
description "Test Product"
quantity {(1..9999).to_a.sample}
price_per_unit {((1..999999).to_a.sample)/100.to_f}
end
end
Do you have any "before(:all)" block? maybe you are missing the corresponding "after(:all)" to clean the data. before(:each) acts as a transaction so the database gets cleaned, before(:all) works like a normal query, yout have to handle the cleanup in an after(:all) block.

My factories are being autogenerated when I do `rake db:test:prepare`

When I run rake db:test:prepare,
It automagically generates my Factories :
require 'ffaker'
FactoryGirl.define do
factory :user do
sequence(:email) {|i| "marley_child#{i}#gmail.com" }
password 'secret_shhh'
end
factory :brain do
user FactoryGirl.create :user
end
end
And then if I try to run rspec or even access my console with rails c test, I get a validation error :
/activerecord-3.2.6/lib/active_record/validations.rb:56:in `save!': Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
My Rspec :
describe '#email' do
context 'uniqueness' do
let(:user) { FactoryGirl.build :user, email: 'Foo#Bar.COM' }
subject { user.errors }
before do
FactoryGirl.create :user, email: 'foo#bar.com'
user.valid?
end
its(:messages) { should include(email: ['has already been taken']) }
end
end
What makes no sense to me is I assumed this data was transactional. Why are my factories getting generated when I prepare by data and not within each test? What is the most appropriate way to do this?
Well, one problem is that in your :brain factory definition, you're actually calling FactoryGirl.create :user as part of the definition of the factory when you presumably meant to call it when the factory is invoked (i.e. user {FactoryGirl.create :user}).
As for why there is already a User in the database, I can't answer that except to say that sometimes even if you're running with transactions turned on and things go south, records can be left behind.

Resources