Reputation: 5376
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 forstring
type on columnpassword_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.
Upvotes: 3
Views: 946
Reputation: 2205
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!
Upvotes: 2