3
votes

I am having a problem with the RSpec tests for user model at the end of section 6.3.4 of M. Hartl's Rails tutorial. I am absolutely lost please help.

All of the tests are passing apart from one that I need help with:

My question: Why rspec is getting different results from similar console operations and how can I fix this and pass the test?

Failures:

1) User return value of authenticate method with valid password

Failure/Error: it { should == found_user.authenticate(@user.password) } expected: User id: 1, name: "Example User", email: "[email protected]", created_at: "2013-08-21 21:38:32", updated_at: "2013-08-21 21:38:32", password_digest: "$2a$04$tjcti4yEYFuJbbz9sa.Z9.F/KXJtHU3A8onU1Fhovn3x..."> got: #User id: nil, name: "Example User", email: "[email protected]", created_at: nil, updated_at: nil, password_digest: "$2a$04$6fdtfzgCFYetjShfYKuFIObWo4Uru4xRMkhW7Ow92O.2...">

Finished in 0.64049 seconds 20 examples, 1 failure

Failed examples:

rspec ./spec/models/user_spec.rb:100 # User return value of authenticate method with valid password

When I manually test it in rails console, authenticate method returns exactly the same user with all of the fields matching. Following is my console experiment:

Loading development environment in sandbox (Rails 4.0.0) Any modifications you make will be rolled back on exit irb(main):001:0>

user = User.new(name: "Example User", email: "[email protected]", password: "foobar", password_confirmation: "foobar")

=> #

irb(main):002:0> user.save
(0.3ms) SAVEPOINT active_record_1 User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('[email protected]') LIMIT 1 Binary data inserted for string type on column password_digest

SQL (48.2ms) INSERT INTO "users" ("created_at", "email", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Wed, 21 Aug 2013 21:45:13 UTC +00:00], ["email", "[email protected]"], ["name", "Example User"], ["password_digest", "$2a$10$6tJCohmi7t3OShf/55S5Se98JWvGhJfC1wNAZsc8B6WPP1Zgee0wu"], ["updated_at", Wed, 21 Aug 2013 21:45:13 UTC +00:00]]

(0.2ms)RELEASE SAVEPOINT active_record_1 => true

irb(main):003:0> found_user = User.find_by_email(user.email)

User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."email" = '[email protected]' LIMIT 1 => #

irb(main):004:0> user == found_user.authenticate(user.password)

=> true

This is what I have in spec/models/user_spec.rb

require 'spec_helper'

describe User do

  before { @user = User.new(name: "Example User", email: "[email protected]", password: "foobar", password_confirmation: "foobar") }

  subject { @user }
.
.
.

  describe "return value of authenticate method" do
    before { @user.save }
    let(:found_user) { User.find_by_email(@user.email) }

    describe "with valid password" do
      it { should == found_user.authenticate(@user.password) }
    end

    describe "with invalid password" do
      let(:user_for_invalid_password) { found_user.authenticate("invalid") }

      it { should_not == user_for_invalid_password }
      specify { user_for_invalid_password.should be_false }
    end
  end

.
.
.
end

User model is as follows

class User < ActiveRecord::Base

  before_save { |user| user.email = email.downcase }

  validates :name, presence: true, length: { maximum: 50 }

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }

  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
  validates :password_confirmation, presence: true

end

Not sure if gem is important, here is what I have in mine:

source 'https://rubygems.org'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.0.0'

group :development do
  gem 'annotate', '~> 2.4.1.beta'
end

# Use sqlite3 as the database for Active Record
gem 'sqlite3'

# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.0'

# Bootstrap css framework from Twitter
gem 'bootstrap-sass', '2.0.0'

# RSpec testing framework
gem 'rspec-rails', '2.11.0'
gem 'capybara', '2.1.0'

# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'

# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.0.0'

# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'

# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'

# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.2'

group :doc do
  # bundle exec rake doc:rails generates the API under doc/api.
  gem 'sdoc', require: false
end

# Use ActiveModel has_secure_password
gem 'bcrypt-ruby', '3.0.1'

Thank you in advance, please let me know if I missed out on any other important bits of info.

1
You might want to change your question title to "Why rspec is getting different results from similar console operations and how can I fix this and pass the test?", the current title isn't a question and doesn't make sensenumber5
You got a point thereDmitry Matveev

1 Answers

2
votes

The immediate problem you're having is how you're setting up your tests. The subject is being set as the unsaved instantiation of the object. If you were to do a before { subject.reload } after your @user.save then it would fix this. That's really just a bandaid.

The real problem is you're over complicating your spec. Your setting the same user in three different places in three different ways in three different states. This should never happen.

Here's how I'd set this up:

require 'spec_helper'

describe User do    
  describe "#authenticate" do
    let(:user) { User.new(name: "Example User", email: "[email protected]", password: "foobar", password_confirmation: "foobar") }

    it "has a valid password" do
      user.authenticate("foobar").should be_true
    end

    it "has invalid password" do
      user.authenticate("invalid").should be_false
    end
  end
end

Just a couple of notes to keep in mind.

Create as few objects as possible: The more you create the more likely something isn't needed or is in a bad state and your tests fail or even worse your tests are fragile and fail unexpectedly.

Single line it is fine, but limiting: Single line it blocks are great when you need to do some quick validation, but you should use them sparingly. If you find that you're wrapping an it block in a describe then get rid of that describe

Keep it simple: Try not to front load your tests. When you front load more than likely you're writing tests that conform to the data.

There a bunch of other great tips over at http://betterspecs.org/. By no means take this as law, but it does have some good information on setting a really good foundation for writing specs.

Lastly, good luck and enjoy writing specs!