UPDATED: I realise now that I've been misreading the diff, and I have a string or symbol on one side of the comparison. Still unsure how I should be putting the expectation in this test however..
I'm new to Rspec and TDD in general, and I've run into this problem. I have a controller that does this:
def index
#users = User.page(params[:page])
end
(I'm using Kaminara to paginate)
And a spec:
describe "when the user DOES have admin status" do
login_admin_user
it "should allow the user access to the complete user list page" do
get :index
response.response_code.should == 200
end
describe "and views the /users page" do
before(:each) do
User.stub(:page) {[ mock_model(User), mock_model(User), mock_model(User) ]}
end
it "should show all users" do
get :index
assigns (:users).should =~ User.page
end
end
end
The spec fails with the following:
Failure/Error: assigns (:users).should =~ User.page
expected: [#<User:0x5da86a8 #name="User_1004">, #<User:0x5d9c90c #name="User_1005">, #<User:0x5d93ef6 #name="User_1006">]
got: :users (using =~)
Diff:
## -1,4 +1,2 ##
-[#<User:0x5da86a8 #name="User_1004">,
- #<User:0x5d9c90c #name="User_1005">,
- #<User:0x5d93ef6 #name="User_1006">]
+:users
Those result sets look identical. Why does this spec fail? Thanks in advance!
I think the problem is the space after assigns. It's comparing the symbol :users to your list. Change it to:
assigns(:users).should =~ User.page
And just a note on how to read Rspec failures. The part after expected, is what you gave to should, whereas the part after got is the value your code actually produced. So it's clear from the report that the result sets were not identical.
Related
When I run the following test
RSpec.describe LessonsController, type: :controller do
describe 'GET / index' do
let(:lesson1) {FactoryGirl.create(:lesson)}
let(:lesson2) {FactoryGirl.create(:lesson)}
it 'returns an http success' do
get :index
expect(response).to be_success
end
it 'returns all the lessons' do
get :index
expect(assigns[:lessons]).to eq([])
expect(assigns[:lessons]).to eq([lesson1, lesson2])
end
end
end
The second expect, expect(assigns[:lessons]).to eq([lesson1, lesson2]), fails with expected: [#<Lesson id:...>, #<Lesson id:...>] got: #<ActiveRecord::Relation []>.
But then, when I run the following test and it all passes
RSpec.describe LessonsController, type: :controller do
describe 'GET / index' do
let(:lesson1) {FactoryGirl.create(:lesson)}
let(:lesson2) {FactoryGirl.create(:lesson)}
it 'returns an http success' do
get :index
expect(response).to be_success
end
it 'returns all the lessons' do
get :index
expect(assigns[:lessons]).to eq([lesson1, lesson2])
end
end
end
I am wondering why is it that the second test does not fail? I was expecting the second spec to also fail with the same reason as the first one.
I believe it might be due to the let statement.
With that said, I am running rspec-rails, factory_girl_rails and Rails 4. I don't believe it is due to contamination because this effect still occurs even when I run the test in isolation (focus).
First, I'm guessing your controller has some code like this:
#lessons = Lesson.all
Remember, that returns an ActiveRecord::Relation which may not actually hit the database until the last moment it needs to. Also, once an ActiveRecord::Relation fetches its results, it will not re-fetch them unless you call .reload.
Secondly, remember how let works. Code for a let isn't evaluated until you try to access that a variable. So, you get a situation like this:
describe "Something" do
let(:lesson) { Lesson.create! }
it "makes a lesson" do
# right now there are 0 lessons
lesson
# calling `lesson` caused the lesson to be created,
# now there is 1 lesson
end
end
Third, when you turn an ActiveRecord::Relation into an Array, it executes the real database query (in this case, select * from lessons).
With those things in mind, we can contrast the two test cases.
In the first case, lessons are fetched from the database before the lessons are actually created:
it 'returns all the lessons' do
get :index
# No lessons have been created yet
# `select * from lessons` returns no results
expect(assigns[:lessons]).to eq([])
# `lessons` is cached. It won't query the database again
# calling `lesson1` and `lesson2` creates two lessons, but it's too late
# the result has already been cached as []
expect(assigns[:lessons]).to eq([lesson1, lesson2])
end
In the second case, the lessons are created first, then the database query is executed:
get :index
# calling `lesson1` and `lesson2` creates two lessons
# then the AR::Relation runs a query and finds the two lessons
expect(assigns[:lessons]).to eq([lesson1, lesson2])
To demonstrate this, here is an example that should pass:
get :index
expect(assigns[:lessons]).to eq([])
# this causes the lessons to be created
lessons = [lesson1, lesson2]
# use `.reload` to force a new query:
expect(assigns[:lessons].reload).to eq(lessons)
Also, you could use RSpec's let! to create the lessons before running the example.
I'd like to isolate specific nodes to test on.
e.g. instead of
get :show
response.should have_content(#user.name)
it would be more descriptive/correct to be able to write something like
get :show
profile = response.find_selector("div.user-profile")
profile.should have_content(#user.name)
is it possible?
UPDATE
Got a bit further with this after reading Peter's answer but still not finding elements.
in app\views\users\index.html.erb
<h1>Users</h1>
<div id="test"></div>
in spec\controllers\users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
it "should should have header" do
get :index
response.should have_selector("h1", content: "Users")
end
it "should show user profile" do
get :index
node = page.find_by_id("test")
p node
end
end
The first test passes, the second test gives ElementNotFound error. I'm possibly just doing something stupid as this is my first go at Rails.
Yes, it is possible. Capybara doesn't have find_selector, but it does have find and derivatives which take a locator and behave as you imply above. See http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders
For example, instead of:
page.should have_selector('foo', text: 'bar')
you can say:
node = page.find('foo')
node.should have_content('bar')
I know this would be a really newbie question, but I had to ask it ...
How do I chain different conditions using logic ORs and ANDs and Rspec?
In my example, the method should return true if my page has any of those messages.
def should_see_warning
page.should have_content(_("You are not authorized to access this page."))
OR
page.should have_content(_("Only administrators or employees can do that"))
end
Thanks for help!
You wouldn't normally write a test that given the same inputs/setup produces different or implicit outputs/expectations.
It can be a bit tedious but it's best to separate your expected responses based on the state at the time of the request. Reading into your example; you seem to be testing if the user is logged in or authorized then showing a message. It would be better if you broke the different states into contexts and tested for each message type like:
# logged out (assuming this is the default state)
it "displays unauthorized message" do
get :your_page
response.should have_content(_("You are not authorized to access this page."))
end
context "Logged in" do
before
#user = users(:your_user) # load from factory or fixture
sign_in(#user) # however you do this in your env
end
it "displays a permissions error to non-employees" do
get :your_page
response.should have_content(_("Only administrators or employees can do that"))
end
context "As an employee" do
before { #user.promote_to_employee! } # or somesuch
it "works" do
get :your_page
response.should_not have_content(_("Only administrators or employees can do that"))
# ... etc
end
end
end
I have an Ruby on Rails 3 admin_controller with the default set of CRUD, index and so on methods. I'd like to test each of these for certain assertions with rspec.
Like response.should render_template("layouts/some_layout") or tests that it should require login.
Copy-pasting that test into the group of tests for each method is a lot of duplication. IMO it makes little sense to have an
it 'should require login' do
Duplicated several times troughout that test.
Is there a simple way to run a test on a list of methods? Say defined_methods.each do |method| it 'should' .... of some sort?
Is this a good way in the first place? Or am I taking a wrong route in the first place?
Given that you really want all those assertions, have you considered shared example groups?
shared_examples_for "an action that requires authentication" do
it "should render successfuly" do
sign_in(user)
response.should be_success # or whatever
end
it "should deny access" do
# don't sign_in the user
# assert access was denied
end
end
shared_examples_for "another behaviour" do
# ...
end
let(:user) { create_user }
describe "#index" do
before(:each) { get :index }
it_behaves_like "an action that requires authentication"
it_behaves_like "another behaviour"
end
describe "#show" do
before(:each) { get :show }
it_behaves_like "an action that requires authentication"
end
# ...
Of course before writing large number of specs for a basic functionality you should always check if it isn't already tested by the library that is providing the functionality (e.g. checking for the rendered template, if it is handled by rails's implicit rendering, might be a bit overkill).
If you wanted to go down the route of iteratively testing each public method in the controller, you could do something like:
SomeController.public_instance_methods(false).each do |method|
it "should do something"
end
However, I think a shared example group (see about half way down this page: http://rspec.info/documentation/) would be prettier. If it were extracted so it could be used across all your controller specs, it'll be even nicer..
shared_examples_for "admin actions" do
it "should require login"
end
Then in each controller spec:
describe SomeController do
it_should_behave_like "admin actions"
end
Just add it to your test_helper.rb, something like:
def requires_login
...
end
i am working in Rspec of ROR..
I am trying to test my controllers using RSpec.i am having a Users controller with functions like new , tags, etc..
i created a file under spec/users_controller_spec.rb
and added the test cases as.
require 'spec_helper'
describe UsersController do
integrate_views
it "should use UsersController" do
controller.should be_an_instance_of(UsersController)
end
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
it "should have the title" do
get 'new'
response.should have_tag("title", "First app" )
end
end
end
which gets pass.
But when i add a test case for tags ..
like
describe "GET 'tags'" do
it "should be successful" do
get 'tags'
response.should be_success
end
end
this results in an error as
F...
1)
'UsersController GET 'tags' should be successful' FAILED
expected success? to return true, got false
why it is coming like this ?? i am very new to ROR and cant find the reason of why i am getting this error..
How to make this pass .
Also i tried the Url
http://localhost:3000/users/tags which is running for me .. But on testing using $spec spec/ i am getting the error ..
Your test may be failing for any number of reasons. Does the route require an ID in the parameter hash? Is the controller action redirecting? Is the controller raising an error?
You'll need to look at the controller code /and/or routes.rb to discover the cause of the failure. Take note of before filters in the controller, which may not allow the action to be reachable at all.
You need to add custom routes that are not within the default 7 routes. Assuming you have resources :users within your routes you will need to modify it. I'm also assuming that your tags route is unique to individual users.
resources :users do
member do
# creates /users/:user_id/tags
get :tags
end
end
And in your RSpec test you would call it like
describe '#tags' do
user = create :user
get :tags, user_id: user.id
end
If the route is not to be unique per user the other option is a collection route, something like:
resources :users do
collection do
# creates /users/tags
get :tags
end
end