I have been writing some rspec tests, and right now I want to verify a redirect after user creation. Many other tests work, including creating users.
However, the last of the following tests fail:
describe "success" do
before(:each) do
#attr = { :username => "woweee123",
:email => "email#mail.com",
:password => "password123",
:password_confirmation => "password123"}
end
it "should create the user" do
lambda do
post :create, :user => #attr
end.should change(User, :count)
end
it "should redirect to a user show page after creation" do
lambda do
post :create, :user => #attr
end.should redirect_to(user_path(assigns(:user)))
end
end
The failed test is: No route matches {:action=>"show", :controller=>"users"
But, when I create a user manually (on localhost) the redirect obviously works. Why is this failing?
I would check if
assigns(#user).new_record?
is not true anymore after the post. It seems like it doesn't have an id in your test. Could some validation have failed?
For Those interested the answer is that I had to change the test so that it looks like:
it "should redirect to a user show page after creation" do
post :create, :user => #attr
response.should redirect_to(user_path(assigns(:user)))
end
This works.
Related
I've been learning Rails 3 with Devise and, so far, seem to have it working quite well. I've got custom session & registration controllers, recaptcha is working and a signed-in user can upload an avatar via carrierwave, which is saved on S3. Pretty happy with my progress.
Now I'm writing Rspec tests. Not going so well! I have a reasonable User model test, but that's because I found it online (https://github.com/RailsApps/rails3-devise-rspec-cucumber/) and was able to add to it by following Michael Hartl's excellent "Ruby on Rails 3 Tutorial".
My real problem is controller test and integration tests, especially controller tests. Initially I thought I'd be able to convert the tests in Michael's book, and I have to a small degree, but it's slow progress and I seem to be constantly hitting my head against a brick wall - partly, I think, because I don't know Rspec and capybara so well (have made some very dumb mistakes) but also because I don't really understand Devise well enough and am wondering if Devise plays as nicely as it might with Rspec; I read somewhere that, because Devise is Rack based, it might not always work as one might expect with Rspec. Don't know if that's true or not?
I know some people will wonder why this might be necessary since Devise is a gem and therefore already tested but I've had a couple of instances where changes elsewhere have broken login or registration without me immediately realizing. I think a good set of controller & integration tests would have solved this.
If I was able to do this myself I would and I'd publish it for others but, so far, writing these tests has been extremely painful and I really need to move on to other things.
I'm sure I wouldn't be the only one who could use this. Anyone know of a such a suite of tests?
In response to Jesse's kind offer of help...
Here is my registrations_controller_spec. The comments in "should render the 'edit' page" show the sort of things I am struggling with. Also, "should create a user" has some things I've tried to test but not been able to:
require File.dirname(__FILE__) + '/../spec_helper'
describe Users::RegistrationsController do
include Devise::TestHelpers
fixtures :all
render_views
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe "POST 'create'" do
describe "failure" do
before(:each) do
#attr = { :email => "", :password => "",
:password_confirmation => "", :display_name => "" }
end
it "should not create a user" do
lambda do
post :create, :user_registration => #attr
end.should_not change(User, :count)
end
it "should render the 'new' page" do
post :create, :user_registration => #attr
response.should render_template('new')
end
end
describe "success" do
before(:each) do
#attr = { :email => "user#example.com",
:password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
response.should redirect_to(root_path)
#response.body.should have_selector('h1', :text => "Sample App")
#response.should have_css('h1', :text => "Sample App")
#flash[:success].should == "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
#response.should have_content "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
end.should change(User, :count).by(1)
end
end
end
describe "PUT 'update'" do
before(:each) do
#user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in #user
end
describe "Failure" do
before(:each) do
# The following information is valid except for display_name which is too long (max 20 characters)
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
end
it "should render the 'edit' page" do
put :update, :id => subject.current_user, :user => #attr
# HAVE PUT THE DEBUGS THAT I'D LIKE TO GET WORKING FIRST
# Would like to be able to debug and check I'm getting the error(s) I'm expecting
puts subject.current_user.errors.messages # doesn't show me the errors
# Would like to be able to debug what html is being returned:
puts page.html # only return the first line of html
# Would like to be able to determine that this test is failing for the right reasons
response.should have_content "Display name is too long (maximum is 20 characters)" # doesn't work
response.should render_template('edit')
end
end
describe "Success" do
it "should change the user's display name" do
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
put :update, :id => subject.current_user, :user => #attr
subject.current_user.reload
response.should redirect_to(root_path)
subject.current_user.display_name == #attr[:display_name]
end
end
end
describe "authentication of edit/update pages" do
describe "for non-signed-in users" do
before(:each) do
#user = FactoryGirl.create(:user)
end
describe "for non-signed-in users" do
it "should deny access to 'edit'" do
get :edit, :id => #user
response.should redirect_to(new_user_session_path)
end
it "should deny access to 'update'" do
put :update, :id => #user, :user => {}
response.should redirect_to(new_user_session_path)
end
end
end
end
end
Acording to Devise Wiki, controller tests have to be some kind of hibrid (unit + integration) tests. You have to create an instance of User (or you auth entity) in the database. Mocking Devise stuff is really hard, trust me.
Try this: How-To:-Controllers-and-Views-tests-with-Rails-3-and-rspec
With Cucumber, you can create a step that already do the login process.
Hope it helps.
OK, so a couple of things to make this work:
Here's how you should test that the body have specific text (you were missing the .body
response.body.should include "Display name is too long (maximum is 30 characters)"
You could also test that the #user has an error:
assigns[:user].errors[:display_name].should include "is too long (maximum is 30 characters)"
But, you do need to actually make the name be over 20/30 characters long, so I changed the attributes being posted to:
#attr = { :email => #user.email, :display_name => ("t" * 35), :current_password => #user.password }
https://gist.github.com/64151078628663aa7577
I have the following route:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks",
:registrations => 'users/registrations',
:sessions => "users/sessions" }
and the following controller test (registrations_controller_spec.rb):
require File.dirname(__FILE__) + '/../spec_helper'
describe Users::RegistrationsController do
include Devise::TestHelpers
fixtures :all
render_views
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe "POST 'create'" do
describe "success" do
before(:each) do
#attr = { :email => "user#example.com",
:password => "foobar01", :password_confirmation => "foobar01", :display_name => "New User" }
end
it "should create a user" do
lambda do
post :create, :user => #attr
response.should redirect_to(root_path)
end.should change(User, :count).by(1)
end
end
end
describe "PUT 'update'" do
before(:each) do
#user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in #user
end
describe "Success" do
it "should change the user's display name" do
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
put :update, :id => #user, :user => #attr
puts #user.errors.messages
#user.display_name.should == #attr[:display_name]
end
end
end
end
Now, when I run rspec spec I get (what I think) are weird results:
The "should create a user" test passes. The user count has increased by 1.
However, my "should change the user's display name" fails as follows:
1) Users::RegistrationsController PUT 'update' Success should change the user's display name
Failure/Error: #user.display_name.should == #attr[:display_name]
expected: "Test"
got: "Boyd" (using ==)
And the weird bit is that my statement:
puts #user.errors.messages
Renders the following message:
{:email=>["was already confirmed, please try signing in"]}
What's going on? The user is signed in! That's proved by the fact that the Rspec error returned the display_name of "Boyd". And why is it displaying a message looks as though it's related with account confirmation as opposed to updating a user's details?
Any help would be greatly appreciated!
This works. Thanks holtkampw for seeing what I wasn't! I put some extra code in there just to double check and everything is well!
it "should change the user's display name" do
subject.current_user.should_not be_nil
#attr = { :email => #user.email, :display_name => "Test", :current_password => #user.password }
puts "Old display name: " + subject.current_user.display_name
put :update, :id => subject.current_user, :user => #attr
subject.current_user.reload
response.should redirect_to(root_path)
subject.current_user.display_name == #attr[:display_name]
puts "New display name: " + subject.current_user.display_name
end
I've got a problem with my controller tests for my Courses Controller. It seems that devise is not signing in my user correctly. All the generated controller tests for this controller only are failing.
I create my user in users.rb using Factory-girl as per below...
FactoryGirl.define do
factory :user do
sequence :email do |n|
"test#{n}#email.com"
end
password "password"
password_confirmation "password"
end
end
Then in my courses_controller_spec.rb I simulate the login as per below..
require 'spec_helper'
describe CoursesController do
include Devise::TestHelpers
before(:each) do
##request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory.create(:user)
user.toggle!(:admin)
sign_in user
end
describe "DELETE destroy" do
it "redirects to the courses list" do
course = Factory.create(:course)
delete :destroy, {:id => course.to_param}, valid_session
response.should redirect_to(courses_url)
end
end
And I get the output...
Failure/Error: response.should redirect_to(courses_url)
Expected response to be a redirect to <http://test.host/courses> but was a redirect to <http://test.host/users/sign_in>
Please note I've also used the following in my spec_helper.rb
config.include Devise::TestHelpers, :type => :controller
And I've tried it as per https://github.com/plataformatec/devise/wiki/How-To:-Controllers-and-Views-tests-with-Rails-3-%28and-rspec%29
In my request specs I can create the user and login using the below which works fine but I'd like to get all the controller tests working also
fill_in "Email", :with => user.email
fill_in "Password", :with => user.password
click_button "Sign in"
Any help here would be much appreciated.
I just figured this out myself. Remove the valid_session from your delete call. That seems to overwrite the session defined using the devise test helpers.
Everything else seems correct. Keep all the devise setup code and just change the delete line in your spec to:
delete :destroy, {:id => course.to_param}
I also kept in the following line in my before( :each ) block, which you commented out. Not sure what it does yet:
#request.env["devise.mapping"] = Devise.mappings[:user]
I'm testing my controllers using Rspec and I can't seem to set the session variable of the current controller under test before making the request to the path.
For example this works:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
get controller_path
request.session[:state] = "12334"
end
end
This doesn't work (i get an error saying session is not a method of Nil class):
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
request.session[:state] = "12334"
get controller_path
end
end
Any ideas?
With new version of RSpec this is done pretty nice, look:
describe SessionController do
# routes are mapped as:
# match 'login' => 'session#create'
# get 'logout' => 'session#destroy'
describe "#create" do
context "with valid credentials" do
let :credentials do
{ :email => 'example#gmail.com', :password => 'secret' }
end
let :user do
FactoryGirl.create(:user, credentials)
end
before :each do
post '/login', credentials
end
it "creates a user session" do
session[:user_id].should == user.id
end
end
# ...
end
describe "#destroy" do
context "when user logged in" do
before :each do
get "/logout", {}, { :user_id => 123 } # the first hash is params, second is session
end
it "destroys user session" do
session[:user_id].should be_nil
end
# ...
end
end
end
You can also use simply request.session[:user_id] = 123 inside before(:each) block, but above looks pretty nicer.
Try this:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
session[:state] = "12334"
get controller_path
end
end
My controller spec fails because Factory Girl seems to be creating non-unique Users even though I sequence the User attributes that need to be unique.
The Errors
1) TopicsController POST #create when topic is invalid should render new
Failure/Error: let(:invalid_topic) {Factory.build :invalid_topic}
ActiveRecord::RecordInvalid:Validation failed: Email has already been taken, Username has already been taken
2) TopicsController POST #create when topic is valid should redirect to show
Failure/Error: let(:valid_topic) {Factory.build :topic}
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken, Username has already been taken
The Controller Spec (RSpec)
describe "POST #create" do
let(:valid_topic) {Factory.build :topic}
let(:invalid_topic) {Factory.build :invalid_topic}
context "when topic is invalid" do
it "should render new" do
post :create, :topic => invalid_topic
response.should render_template(:new)
end
end
context "when topic is valid" do
it "should redirect to show" do
post :create, :topic => valid_topic
response.should redirect_to(topic_path(assigns(:topic)))
end
end
end
The Factories
Factory.define :user do |f|
f.sequence(:username) { |n| "foo#{n}"}
f.password "password"
f.password_confirmation { |u| u.password}
f.sequence(:email) { |n| "foo#{n}#example.com"}
end
Factory.define :topic do |f|
f.name "test topic"
f.association :creator, :factory => :user
f.forum_id 1
end
Why isn't Factory Girl sequencing the User attributes when I use Factory.create :topic?
rake db:test:prepare seemed to fix the problem.
Not sure why, though. The schema hadn't been changed.
Please, consider using database_cleaner gem. One was designed specifically to fulfill the purpose of cleaning up database between test runs.
This post explains pretty much everything.
You should consider deleting all topics by hand in the end of the test. Of course, it is not number one solution but worked out for me real great.
after(:all) { Topic.delete_all }