0

Edit

Using the answers to the question I changed the test to the following which tests correctly and passes..

describe "when email is already taken" do
  let(:user_with_same_email) { @user.dup }
  before do
    user_with_same_email.email.upcase!
    user_with_same_email.save
  end

  it { user_with_same_email.should_not be_valid }
end

Note: Not using let(:user_with_same_email) { @user.dup } makes the test fail as it cannot find the variable user_with_same_email if it's simply duplicated in the before block as in the chosen answer to this question.


I have a User model and a user_spec.rb test file which has various validations against the User models attributes.

Previously I was writing the following at the top of my user_spec.rb file to test the User model:

describe User do

  before do
  @user = User.new(name: "Example User", email: "user@example.com",
                 password: "foobar88", password_confirmation: "foobar88")
end
...

I wanted to move this model creation to FactoryGirl so I created a factories.rb file:

FactoryGirl.define do
  factory :user do
    name "foo"
    email { "#{name}@example.com" }
    password "foobar99"
    password_confirmation "foobar99"
  end
end

I then changed my user_spec.rb:

describe User do

  before do
    @user = FactoryGirl.create(:user)
  end
...

Now every test passes as before except one:

describe "when email is already taken" do
  before do
    user_with_same_email = @user.dup
    user_with_same_email.email = @user.email.upcase
    user_with_same_email.save
  end

  it { should_not be_valid }
end

Now unless `FactoryGirl is skipping my email uniqueness validation I can't figure out what is going wrong here.

My User model validation code:

class User < ActiveRecord::Base

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i unless const_defined?(:VALID_EMAIL_REGEX)

  has_secure_password
  attr_accessible :name, :email, :password, :password_confirmation

  has_many :programs

  before_save { self.email.downcase! }

  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
Ashley
  • 2,256
  • 1
  • 33
  • 62

2 Answers2

3

The problem is that when you say it { should_not be_valid }, RSpec checks the subject. in this case the subject is User.new (you have "describe User" at the top so unless you specified something else this is the default).

You want to check the user_with_same_email for validity instead.

edit: Try this, I think it might work:

describe "when email is already taken" do
  before do
    @user_with_same_email = @user.dup
    @user_with_same_email.email = @user.email.upcase
    @user_with_same_email.save
  end

  it { @user_with_same_email.should_not be_valid }
end
davidrac
  • 10,723
  • 3
  • 39
  • 71
  • `User.new` was replaced with `FactoryGirl.create(:user)` – Ashley Jul 07 '12 at 21:23
  • I changed the test code to something I think might work. Please try it. – davidrac Jul 08 '12 at 17:56
  • You are correct. The `it` function checks the validity of the subject which is valid and not the duplicated `user_with_same_email` instance which is invalid. Thank you. – Ashley Jul 09 '12 at 13:01
2

Looks like perhaps you're doing (or referencing) Michael Hartl's Rails Tutorial. Here's what my code looks like for what you're doing, so I hope it can be of use:

spec/models/user_spec.rb

describe User do

  let(:user) { valid_user }
  subject { user }

  # ...

  context "when email address is already taken" do
    before { save_user(user) }
    it { should_not be_valid }
  end

  # ...
end

spec/support/utilities.rb (to create a specific user)

def valid_user
  User.new(name:     "Example User", 
           email:    "user@example.com",
           password: "foobar", 
           password_confirmation: "foobar")
end

# ...

def save_user(user)
  user_with_same_email = user.dup
  user_with_same_email.email.upcase!
  user_with_same_email.save
end

For reference: spec/factories.rb (to just create any old random user)

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com" }
    password "foobar"
    password_confirmation "foobar"

    # ...
  end
  # ...
end

Update: Found the answer you were looking for at this StackOverflow answer outlining the same problem. I tested it with my code as well and it worked for me.

Update 2: Changed my code around as well, using FactoryGirl.build for times when I want a user but don't want it saved to the database. This StackOverflow answer helped me understand.

spec/models/user_spec.rb

describe User do

  let(:user) { FactoryGirl.create(:user) }
  subject { user }

  # ...

  context "when email address is already taken" do
    let(:user_with_same_email) do
      FactoryGirl.build(:user, email: user.email)
    end

    subject { user_with_same_email }

    before do
      user_with_same_email.email.upcase!
      user_with_same_email.save
    end

    it { should_not be_valid }
  end
  # ...
end

Thanks for asking this question. Gave me some food for thought and some refactoring to do in my own code.

Community
  • 1
  • 1
Paul Fioravanti
  • 16,423
  • 7
  • 71
  • 122
  • This is the same as my initial code except code has been extracted into 2 helper functions. I don't see where you invoke FactoryGirl in your code. – Ashley Jul 07 '12 at 22:06
  • I don't invoke FactoryGirl in this case (I just included it above for reference) cause I wanted a specific user with the same values in its attributes for all the other tests in the `user_spec`. I just use FactoryGirl when I want to pull some nondescript user out of thin air to do something with. – Paul Fioravanti Jul 07 '12 at 22:17
  • The only other things I can think of are changing your `before` loop to a `let` and then specifically marking a `subject`, or perhaps changing your `it` block to `@user.should_not be_valid`. – Paul Fioravanti Jul 07 '12 at 22:25
  • Results are the same. All tests pass as before except the email uniqueness test. – Ashley Jul 07 '12 at 23:03
  • Can you edit your answer to post the error message? Maybe try commenting out your `unless const_defined?(:VALID_EMAIL_REGEX)` (do you need it?)? Otherwise, perhaps just go back to the [uniqueness validation](http://ruby.railstutorial.org/book/ruby-on-rails-tutorial#sec:uniqueness_validation) part of the book, get it working with the code there and then begin customizing again. – Paul Fioravanti Jul 08 '12 at 11:36
  • `'unless const_defined?(:VALID_EMAIL_REGEX)'` just gets rid of error messages with `guard`. It's not responsible for the error (in fact that test passes). Actually hard coding the email values makes the test pass, also printing both emails results in 2 identical strings. This appears to be an error with `FactoryGirl`. – Ashley Jul 08 '12 at 13:19
  • I don't think it's an error in `FactoryGirl`; it's just the way you make a `user` (`create` vs `build`). See the edit to my answer. – Paul Fioravanti Jul 09 '12 at 06:03
  • I didn't realise the relations between `it` and `subject` since I am new to `RSpec`. See the chosen answer for the reason why my test fails. Thank you for you for taking the time to answer. – Ashley Jul 09 '12 at 13:03