RSpec doesn't compare objects properly - ruby-on-rails

I have the following spec:
require 'spec_helper'
describe Page do
it "has a valid factory" do
create(:page).should be_valid
end
it "is invalid without a title" do
build(:page, title: nil).should_not be_valid
end
it "finds record" do
page = create(:page, title: 'heyo')
Page.unscoped.where(:title => 'heyo').should == page
end
it "finds record with same attributes" do
page = create(:page, title: 'heyo')
Page.unscoped.where(:title => 'heyo').first.attributes.each do |name, val|
expect(page[name]).to eq(val)
end
end
end
I have the following factory:
model_statuses = ['published', 'draft']
FactoryGirl.define do
factory :page do
title { Faker::Lorem.word + ' Page' }
slug { title ? title.parameterize : nil }
intro { Faker::Lorem.sentence 10 }
content { Faker::Lorem.sentences(5).join ' ' }
status { model_statuses.sample }
end
end
Tests fail with:
Failures:
1) Page finds record with same attributes
Failure/Error: expect(page[name]).to eq(val)
expected: Fri, 24 Jan 2014 23:33:47 MSK +04:00
got: Fri, 24 Jan 2014 23:33:47 MSK +04:00
(compared using ==)
Diff:
# ./spec/models/page_spec.rb:20:in `block (3 levels) in <top (required)>'
# ./spec/models/page_spec.rb:19:in `each'
# ./spec/models/page_spec.rb:19:in `block (2 levels) in <top (required)>'
2) Page finds record
Failure/Error: Page.unscoped.where(:title => 'heyo').should == page
expected: #<Page id: 1, slug: "heyo", title: "heyo", intro: "Sed sint et nesciunt earum libero eveniet est cupid...", content: "A sunt ab exercitationem quas ex incidunt numquam. ...", created_at: "2014-01-24 19:33:47", updated_at: "2014-01-24 19:33:47", status: "draft">
got: [#<Page id: 1, slug: "heyo", title: "heyo", intro: "Sed sint et nesciunt earum libero eveniet est cupid...", content: "A sunt ab exercitationem quas ex incidunt numquam. ...", created_at: "2014-01-24 19:33:47", updated_at: "2014-01-24 19:33:47", status: "draft">] (using ==)
Why these objects and their attributes aren't the same and how do I properly check for object equality?

I see the problems to be in comparison of different objects in your tests.
The following test:
it "finds record" do
page = create(:page, title: 'heyo')
Page.unscoped.where(:title => 'heyo').should == page
end
When you get to this test you already have one page object from "has a valid factory" test. So after this test executes you have two page objects, first with title nil and second with title heyo. Now when you try to match with should in the next line your Page.unscoped.where(:title => 'heyo') does return expected result, i.e. an array of Page objects with title 'heyo'. You're getting an array and you're comparing the array with an Object, so that's going to fail.
Then the reason your second test "finds record with same attributes" is failing is because you are expecting the first Page object to be equal to last Page object, so this is also going to fail.
So the fixes would be on the following two tests:
it "finds record" do
page = create(:page, title: 'heyo')
Page.unscoped.where(:title => 'heyo').last.should == page
end
it "finds record with same attributes" do
page = create(:page, title: 'heyo')
Page.unscoped.where(:title => 'heyo').last.attributes.each do |name, val|
expect(page[name]).to eq(val)
end
end
Note the use of last in both tests.
The database_cleaner gem is something that you might be interested in. It allows you to clean the database before each tests. Recommend looking at it.

The source of both errors is the same.
You are comparing one of the dates, created_at, updated_at, and they don't seem to have the same value.
Notice that the dates only go to the second when printed as strings - they might differ at a lower level than that.
Either exclude the dates from the comparison, or do a raise/inspect in the code at the site of the failure to see if the fractional seconds are differing between the two items.
Another trick you can use is to stub out Time.now/Time.new within the scope of your spec, so that you will always get a consistent result.
Normally you will stub out/abuse Time.now in a before :each block inside the context of your spec. You want to make the scope as narrow as possible because overriding that method can have all sorts of weird side effects if you aren't careful where you do it.

You must freeze the time in your case, use timecop for instance

Related

Rails Rspec allow multiple method call in one line

desc 'Remove credential state users who no longer request for confirm otp within 10 minutes'
task failed_user_cleaner: :environment do
puts "Daily UserRecord Cleaning CronJob started - #{Time.now}"
#user = User.with_state("credentials").with_last_otp_at(Time.now - 10.minutes)
Users::Delete.new(#user).destroy_all
puts "Daily UserRecord Cleaning CronJob ended - #{Time.now}"
end
Above is crop job rake file code.
then I've tried in many times and found in many times.
But I couldn't find the way to write unit test case for above job.
Help me to write test case correctly.
here is my spec code
require 'rails_helper'
describe 'users rake tasks' do
before do
Rake.application.rake_require 'tasks/users'
Rake::Task.define_task(:environment)
end
context 'when remove credential state users who no longer request for confirm otp within 10 minutes' do
let(:user) { create(:user, last_otp_at: Time.now - 11.minutes, state: "credentials") }
let (:run_users_rake_task) do
Rake.application.invoke_task 'users:failed_user_cleaner'
end
it 'calls right service method' do
#users = Users::Delete.new([user])
expect(#users).to receive(:destroy_all)
run_users_rake_task
end
end
end
here is the error log
Failures:
1) users rake tasks when remove credential state users who no longer request for confirm otp within 10 minutes calls right service method
Failure/Error: expect(#users).to receive(:destroy_all)
(#<Users::Delete:0x0000556dfcca3a40 #user=[#<User id: 181, uuid: nil, phone: "+66969597538", otp_secret: nil, last_otp_at: "2021-09-30 09:32:24.961548000 +0700", created_at: "2021-09-30 09:43:24.973818000 +0700", updated_at: "2021-09-30 09:43:24.973818000 +0700", email: nil, avatar: "https://dummyimage.com/300x300/f04720/153572.png?t...", refresh_token: "eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzI5Njk4MDQsImV4c...", first_name_en: "Jenise", first_name_th: "Damion", last_name_en: "McCullough", last_name_th: "Beatty", nationality: "TH", thai_national_id: nil, thai_laser_code: nil, company_id: 200, role: nil, state: "credentials", date_of_birth: "2020-10-30 00:00:00.000000000 +0700", deleted_at: nil, password_digest: "$2a$04$jfR9X9ci06602tlAyLOoRewTK1lZ12vJ2cZ9Dc2ov4F...", username: "zreejme238", shopname: nil, access_token: nil, locked_at: nil, login_attempts: 0, locale: "th", scorm_completed: false>]>).destroy_all(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/tasks/users_spec.rb:19:in `block (3 levels) in <top (required)>'
You are creating two instances of Users::Delete when running this test, one within the test and one within the task. Since the instance within the test is not used, it is incorrect to expect it to receive a message.
Rspec has an expectation, expect_any_instance_of, that will fix this however consider reading the full page since it can create fragile or flaky tests. If you wanted to use this method, your test would look something like:
it 'calls right service method' do
expect_any_instance_of(Users::Delete).to receive(:destroy_all)
run_users_rake_task
end
Personally I'd instead check that the expected users were deleted with something like:
it 'removes the user' do
expect { run_users_rake_task }.to change { User.exists?(id: #user.id) }.to(false)
end
Unless you want to use any_instance_of (which is a code smell) you need to stub the Users::Delete method so that it returns a double and put the expectation on the double:
require 'rails_helper'
describe 'users rake tasks' do
before do
Rake.application.rake_require 'tasks/users'
Rake::Task.define_task(:environment)
end
context 'when remove credential state users who no longer request for confirm otp within 10 minutes' do
let(:user) { create(:user, last_otp_at: Time.now - 11.minutes, state: "credentials") }
let(:run_users_rake_task) do
Rake.application.invoke_task 'users:failed_user_cleaner'
end
let(:double) do
instance_double('Users::Delete')
end
before do
allow(Users::Delete).to receive(:new).and_return(double)
end
it 'calls right service method' do
expect(double).to receive(:destroy_all)
run_users_rake_task
end
end
end
However this really just tells us that the API of the service object is clunky and that you should write a class method which both instanciates and performs:
module Users
class Delete
# ...
def self.destroy_all(users)
new(users).destroy_all
end
end
end
desc 'Remove credential state users who no longer request for confirm otp within 10 minutes'
#...
Users::Delete.destroy_all(#user)
# ...
end
require 'rails_helper'
describe 'users rake tasks' do
# ...
context 'when remove credential state users who no longer request for confirm otp within 10 minutes' do
# ...
it 'calls right service method' do
expect(Users::Delete).to receive(:destroy_all)
run_users_rake_task
end
end
end

RSpec - Type error - no implicit conversion of String into Integer

I'm trying to make a test green and I'm getting this error. Even when I pry the code it shows that a string is being returned so I have no idea why the test won't green. Here is my test and error for clarification. I'm assuming this is a common error for folks.
Here is my test -
require "rails_helper"
RSpec.describe "/api/retailers" do
describe "GET /api/retailers" do
it "Returns JSON for retailers" do
location = Location.create!(
city: "Portland",
street_1: "Cherry",
state: "Oregon",
zip: "49490"
)
retailer = Retailer.create!(
name: "Good Coffee Co.",
description: "Hipster Good",
image_url: "http://www.example.com/foo_bar.jpg",
location: location
)
get "/api/retailers.json"
expect(response).to be_success
json = JSON.parse(response.body)
expect(json["name"]).to eql("Good Coffee Co.")
expect(json["description"]).to eql("Hipster Good")
expect(json["image_url"]).to eql("http://www.example.com/foo_bar.jpg")
expect(json["location"]).to eql(location.city)
end
end
end
here is my error message -
/api/retailers GET /api/retailers Returns JSON for retailers
Failure/Error: expect(json["name"]).to eql("Good Coffee Co.")
TypeError:
no implicit conversion of String into Integer
# ./spec/requests/retailers_spec.rb:28:in `[]'
# ./spec/requests/retailers_spec.rb:28:in `block (3 levels) in <top (required)>'
As mentioned in the comments, the issue was that the JSON object returns an array of objects. So the proper test expectations would be the following:
expect(json.first["name"]).to eq("Good Coffee Co.")
expect(json.first["description"]).to eq("Hipster Good")
expect(json.first["image_url"]).to eq("http://www.example.com/foo_bar.jpg")
expect(json.first["city"]).to eq("Portland")
It looks like you're hitting an index endpoint which is returning an array of retailers, in your case an array with a single retailer. To access this you'll need to select the first object in the array and then do your comparisons:
expect(json[0]['name']).to eq('Good Coffee Co.')
On a similar note you should be using let and before to setup your tests. Following the rspec conventions will not only make your code more readable, but easier to maintain. I've made style changes below as to how I typically lay out my tests. These are just suggestions of course and make a couple of assumptions as to the naming and scoping of your controller.
require "rails_helper"
RSpec.describe Api::RetailersController do
# /api/retailers
describe '#index' do
let!(:location) {
Location.create!(
city: "Portland",
street_1: "Cherry",
state: "Oregon",
zip: "49490"
)
}
let!(:retailer) {
Retailer.create!(
name: "Good Coffee Co.",
description: "Hipster Good",
image_url: "http://www.example.com/foo_bar.jpg",
location: location
)
before(:each) do
get :index, format: :json
json = JSON.parse(response.body)
end
it 'Returns a status 200' do
expect(response).to be_success
end
it 'Returns a JSON list of retailers' do
expect(json.length).to eq(1)
end
# I would probably only check these in the show action
# unless needing different data on index
it 'Contains a name attribute' do
expect(json[0]['name']).to eq('Good Coffee Co.')
end
it 'Contains a description attribute' do
expect(json[0]['description']to eq('Hipster Good')
end
it 'contains an image_url attribute' do
expect(json[0]['image_url']).to eq('http://www.example.com/foo_bar.jpg')
end
it 'contains a location attribute' do
expect(json[0]['location']).to eq(location.city)
end
end
end

Undefined method confusion

I'm a Rails newbie working on my RSpec skills. I'm running into this undefined error that's leaving me scratching my head. Here it is:
1) Veiwing the list of movies shows the movies
Failure/Error: expect(page).to have_text(Movies.location)
NoMethodError:
undefined method `location' for #<Class:0x00000104bbb958>
# ./spec/features/list_movies_spec.rb:26:in `block (2 levels) in <top (required)>
Below is my spec file. Can you please tell me what am I overlooking here?
require "spec_helper"
feature "Veiwing the list of movies" do
it "shows the movies" do
movie1 = Movies.create(name: "The Butler", location: "New York City",
price:15.00)
movie2 = Movies.create(name: "The Grand Master", location: "New Jersey",
price:15.00)
movie3 = Movies.create(name: "Elysium", location: "Los Angeles",
price:15.00 )
movie4 = Movies.create(name:"Pacific Rim", location: "Queens NY",
price:15.00)
visit movies_url
expect(page).to have_text("4 Movies")
expect(page).to have_text(Movies.name)
expect(page).to have_text(Movies.name)
expect(page).to have_text(Movies.name)
expect(page).to have_text(Movies.name)
expect(page).to have_text(Movies.location)
expect(page).to have_text(Movies.description)
expect(page).to have_text("15.00")
end
end
Movies is a class and thus does not have a value for location. You should be checking the location against an instance of Movies: movie1, 2, 3, or 4. For example:
expect(page).to have_text(movie1.location)

Default_scope in Rails model when testing with Rspec using FactoryGirl

I'm developing a blog in Rails and I'm stuck when I was trying to test the default scope I added to the Post model in order to have the posts in descending order of their creation date.
Post code:
class Post < ActiveRecord::Base
attr_accessible :content, :name, :title
validates :title, presence: true,uniqueness: true
validates :name, presence: true
validates :content, presence: true
default_scope order: "posts.created_at DESC"
end
Rspec code:
describe "Posts descending order of creation date" do
let(:older_post) do
FactoryGirl.create(:post, created_at: 1.day.ago)
end
let(:newer_post) do
FactoryGirl.create(:post, created_at: 1.hour.ago)
end
it "should have the 2 posts in desc order" do
Post.all.should == [newer_post, older_post]
end
end
FactoryGirl definition
FactoryGirl.define do
factory :post do
sequence(:title) { |n| "A book #{n}" }
name "Johnny"
content "Lorem Ipsum"
end
end
The output
.....F...
Failures:
1) Post Posts descending order of creation date should have the 2 posts in desc order
Failure/Error: Post.all.should == [newer_post, older_post]
expected: [#<Post id: 1, name: "Johnny", title: "A book 1", content: "Lorem Ipsum", created_at: "2013-05-01 14:44:45", updated_at: "2013-05-01 15:44:45">, #<Post id: 2, name: "Johnny", title: "A book 2", content: "Lorem Ipsum", created_at: "2013-04-30 15:44:45", updated_at: "2013-05-01 15:44:45">]
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[#<Post id: 1, name: "Johnny", title: "A book 1", content: "Lorem Ipsum", created_at: "2013-05-01 14:44:45", updated_at: "2013-05-01 15:44:45">,
- #<Post id: 2, name: "Johnny", title: "A book 2", content: "Lorem Ipsum", created_at: "2013-04-30 15:44:45", updated_at: "2013-05-01 15:44:45">]
+[]
# ./spec/models/post_spec.rb:54:in `block (3 levels) in <top (required)>'
Finished in 1.03 seconds
9 examples, 1 failure
Failed examples:
rspec ./spec/models/post_spec.rb:53 # Post Posts descending order of creation date should have the 2 posts in desc order
I also want to mention that when I type Post.all in the Rails console, I get the records in descending order ( so as I wanted them).
Can someone give me a suggestion on what the problem might be?
Please know that let is evaluated lazily in RSpec. This often creates problem in such scenarios where ordering is concerned.
Try these two alternatives:
describe "Posts descending order of creation date" do
let!(:older_post) do
FactoryGirl.create(:post, created_at: 1.day.ago)
end
let!(:newer_post) do
FactoryGirl.create(:post, created_at: 1.hour.ago)
end
it "should have the 2 posts in desc order" do
Post.all.should == [newer_post, older_post]
end
end
Note, the use of let! instead of let.
Or, use before as:
describe "Posts descending order of creation date" do
it "should have the 2 posts in desc order" do
#older_post = FactoryGirl.create(:post, created_at: 1.day.ago)
#newer_post = FactoryGirl.create(:post, created_at: 1.hour.ago)
Post.all.should == [#newer_post, #older_post]
end
end
Do let me know if it works or not. :)

Using RSpec to test for correct order of records in a model

I'm new to rails and RSpec and would like some pointers on how to get this test to work.
I want emails to be sorted from newest to oldest and I'm having trouble testing this.
I'm new to Rails and so far I'm having a harder time getting my tests to work then the actual functionality.
Updated
require 'spec_helper'
describe Email do
before do
#email = Email.new(email_address: "user#example.com")
end
subject { #email }
it { should respond_to(:email_address) }
it { should respond_to(:newsletter) }
it { should be_valid }
describe "order" do
#email_newest = Email.new(email_address: "newest#example.com")
it "should have the right emails in the right order" do
Email.all.should == [#email_newest, #email]
end
end
end
Here is the error I get:
1) Email order should have the right emails in the right order
Failure/Error: Email.all.should == [#email_newest, #email]
expected: [nil, #<Email id: nil, email_address: "user#example.com", newsletter: nil, created_at: nil, updated_at: nil>]
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[nil,
- #<Email id: nil, email_address: "user#example.com", newsletter: nil, created_at: nil, updated_at: nil>]
+[]
# ./spec/models/email_spec.rb:32:in `block (3 levels) in <top (required)>'
In your code:
it "should have the right emails in the right order" do
Email.should == [#email_newest, #email]
end
You are setting the expectation that the Email model should be equal to the array of emails.
Email is a class. You can't just expect the class to be equal to an array. All emails can be found by using all method on class Email.
You must set the expectation for two arrays to be equal.
it "should have the right emails in the right order" do
Email.order('created_at desc').all.should == [#email_newest, #email]
end
It should work like this.
For newer version of RSpec:
let(:emails) { ... }
it 'returns emails in correct order' do
expect(emails).to eq(['1', '2', '3'])
end

Resources