Undefined method confusion - ruby-on-rails

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)

Related

How to give a unique id to a test object in Rspec?

context 'with dish available to pre-order' do
let!(:dish) { create :dish, chef: chef }
let!(:dish2) { create :dish, chef: chef2}
it 'should show dish' do
Time.zone = 'Asia/Kuala_Lumpur'
t = Time.zone.local(2016, 7, 25, 20, 30, 0)
Timecop.travel(t)
visit '/'
set_delivery_time
select_delivery_area_by_chef dish.chef
visit '/preorders'
expect(page).to have_content('PREORDER MENU')
within('div#dishes-container') do
expect(page).to have_xpath("//img[#src=\"#{dish.image.small}\"]")
expect(page).to have_content(dish.description)
expect(page).to have_content(dish.name)
expect(page).to have_content(dish.price)
expect(dish).to have_content("Add to Cart")
expect(dish2).to have_content("Pre-Order")
expect(current_path).to eq(preorders_path)
end
end
1) Visitor can see preorders with dish available to add to cart, preparation time over 1 hour should show dish
Failure/Error: expect(dish).to have_content("Add to Cart")
expected to find text "Add to Cart" in "Dock Bogisich"
# ./spec/features/visitor/preorders/list_of_preorders_spec.rb:129:in `block (4 levels) in <top (required)>'
# ./spec/features/visitor/preorders/list_of_preorders_spec.rb:124:in `block (3 levels) in <top (required)>'
I am trying to get rspec to check if specifically dish has 'add to cart' content and dish 2 to have 'pre-order' content. How can I get Rspec to check it? Is it possible to give them both a unique id and call it out here?
In your view assign ids to both the buttons. Something like:
<button id="add_to_cart-{dish.id}"> ADD to Cart </button>
<button id="pre-order-{dish2.id}"> Pre-Order </button>
Than in spec:
add-cart-btn = "button#add_to_cart-#{dish.id}"
with_in add-cart-btn do
page.should have_content 'Add to Cart'
end
Same for the Pre-order!

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

Rspec feature test issue

spec/features/album_spec.rb:
feature "Album Pages" do
given(:album) { create(:album) } # by Factory_Girl
scenario "Edit album" do
visit edit_album_path album
fill_in "Name", with: "bar"
expect {click_button "Update Album"}.to change(Album.last, :name).to("bar")
end
end
error:
1) Album Pages Edit album
Failure/Error: expect {click_button "Update Album"}.to change(Album.last, :name).to("bar")
name should have been changed to "bar", but is now "Gorgeous Granite Table"
# ./spec/features/albums_spec.rb:27:in `block (2 levels) in <top (required)>'
App works fine and if I click the button it redirects to album's site where its name is shown as <h1>. I tried this:
scenario "Edit album" do
visit edit_album_path album
fill_in "Name", with: "bar"
click_button "Update Album"
save_and_open_page
#expect {click_button "Update Album"}.to change(Album.last, :name).to("bar")
end
than I got a page with that bar as <h1> so I don't know what's wrong with my test and I can see in the test.log:
SQL (0.7ms) UPDATE "albums" SET "name" = ?, "updated_at" = ? WHERE "albums"."id" = 1 [["name", "bar"], ["updated_at", Fri, 11 Apr 2014 11:30:00 UTC +00:00]]
Any ideas ?
You need your change validation to also be in block, like this:
scenario "Edit album" do
visit edit_album_path album
fill_in "Name", with: "bar"
expect{click_button "Update Album"}.to change{Album.last.name}
end
After reading drKreso's answer I read gems/rspec-expectations-.../lib/rspec/matchers.rb. There are many usefull examples for change there.
You can either pass receiver and message, or a block, but not both.
I find one other way how it can be done:
scenario "Edit album" do
#album = album
visit edit_album_path #album
fill_in "Name", with: "bar"
expect{
click_button "Update Album"
#album.reload
}.to change(#album, :name).to("bar")
end
But even now I still have confusions why it didn't work on the first way. So if the reload is a key here than does change invoke it for passed receiver somewhere ? I wasn't able to go so deep.

RSpec doesn't compare objects properly

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

RSpec test link_to inside p tag

Is it possible to have a test for the following html structure?
<p class="all-jobs"><%= link_to "<< Back to all jobs", jobs_path %></p>
I tried doing it this way:
it { should have_selector('p.all-jobs', link: "<< Back to all jobs")}
The error is:
1) JobPages visit a job page
Failure/Error: it { should have_selector('p.all-jobs', link: "<< Back to all jobs")}
expected css "p.all-jobs" to return something
# ./spec/requests/job_pages_spec.rb:78:in `block (3 levels) in <top (required)>'
Okay, I got it to work with the following:
describe "should have a link to index page" do
it { should have_selector('a', title: "<< Back to all jobs", href: jobs_path) }
end

Resources