Doubts on Rspec feature when user_id is needed - ruby-on-rails

Say I want to test that a User is able to fill a form to create a new project. The route that I need to visit is: new_user_project_path(:user_id).
So, my feature rspec looks like this right now:
feature "User creates a project" do
scenario "Logged in User creates a project" do
login_with_oauth #this sets current_user
visit new_user_project_path
fill_in 'Title', :with => 'Colchasdadasdasoneta'
fill_in 'Description', :with => 'lalalssalsalas'
click_in 'Create Project'
end
end
As you can tell, I can't use this visit new_user_project_path because it needs a :user_id to be passed. My question is, how can I access the current_user? Or what is the correct way of testing this kind of stuff?

Your routes have problem. Instead of fixing the problem directly which is easy, better to double check your code logic.
You should not define user id as path param to create a new project. Instead, the project instance should be initiated by current_user.
To associate a new project with user, just do it in controller
#project = current_user.projects.new
In conclusion:
Change the controller code similar to above
Restore your routes to conventional resource.
Then, use your test as it is, though a bit more expectations should be appended.

Assuming login_with_oauth is a spec helper method that you wrote, you can have that return the user that it setup. Then you can do:
feature "User creates a project" do
scenario "Logged in User creates a project" do
user = login_with_oauth
visit new_user_project_path(user)
If that doesn't work, and login_with_oauth is something you don't control (say it comes from some library), you can fetch the first user out of the database. This is assuming no other users are in the db, but there shouldn't be because your tests should be clean and autonomous. Then do:
feature "User creates a project" do
scenario "Logged in User creates a project" do
login_with_oauth
user = User.first
visit new_user_project_path(user)

Related

Upgrade to Rails 4.2 breaks rspec feature specs - why?

My model and controller specs are running fine, but after the upgrade to rails 4.2, my feature specs, which use Capybara, no longer work. Example:
#in spec_helper.rb:
def login_admin
create(:user,
first_name: 'John',
last_name: 'Doe',
email: 'doej#test.com',
admin: true)
visit root_path
fill_in 'email', with: 'doej#test.com'
fill_in 'password', with: 'password1'
click_button 'Log in'
puts 'created'
end
#in spec/features/albums_spec.rb
feature "Albums", :type => :feature do
before(:each) do
login_admin
end
scenario 'do something' do
save_and_open_page
end
end
When I run this spec, it never finishes, either pass or fail. No error is thrown; it just sits there, showing Albums with the cursor beneath. 'created' is never put to stdout, and the page is never launched by the save_and_open_page call. Test log shows the erb file is rendered by my login action. This was all working prior to the rails 4.2 upgrade.
This only fails during the spec run - using the app in the browser works fine.
What am I missing here?
UPDATE: possible problems related to capybara/rspec:
Avoid creating models in feature specs, because they're created in a different thread. Instead, create the user using capybara steps (i.e. "sign up" the user every time).
If you really need the database prepared for scenario in a way impossible to create from clicking around the site, implement a simple "admin" area in your app (you'll probably need one anyway) or admin API interface or something like a CSV upload option, etc.
Otherwise, you can search for "capybara rspec database_cleaner append_after" for a setup to support creating models in feature specs, but I've found that none of the solutions are really bullet-proof. You could try: https://github.com/RailsApps/rails_apps_testing
I'm guessing your example is/was stuck on a database operation (waiting for db connection in other threads to end).
PREVIOUS ANSWER:
A few ideas:
catch exceptions in the login_admin method and print them out using STDERR.puts
remove the click_button and see if it fails as expected (shows you the login page)
add a sleep 4 after the password is typed in
separate the click_button into 2 calls (to see which one hangs):
btn = find(:button, locator, options)
STDERR.puts "found: #{btn.inspect}"
btn.click
use bundle show capybara to find out where it is, edit the find (or click) method and put in STDERR.puts methods there to see what's wrong

Testing an API resource with RSpec

I've done a bit of googling on the topic, and I'm still confused.
I'm building a custom help page with the Zendesk API Ruby Client, and I'm at a stage when I need to test the creation of the ZendeskAPI::Ticket resource. The following code is in the spec/features directory. It fills out a form with valid values and submits the form to the #create action. Fairly standard, simple stuff.
require 'spec_helper'
feature 'ticket creation' do
scenario 'user creates new ticket' do
visit root_path
fill_in 'Name', with: 'Billybob Joe'
fill_in 'Email', with: 'joe#test.com'
fill_in 'Subject', with: 'Aw, goshdarnit!'
fill_in 'Issue', with: 'My computer spontaneously blew up!'
click_button 'Create Ticket'
expect(page).to have_content('Ticket details')
end
end
And here is the relevant part of the Tickets controller. The ticket_valid? method supplies minimal validations for the options hash and client is an instance of the ZendeskAPI::Client.
def create
options = { subject: params[:subject], comment: { value: params[:issue] },
requester: params[:email], priority: 'normal' }
if ticket_valid? options
flash[:success] = 'Ticket created.'
#ticket = ZendeskAPI::Ticket.create(client, options)
redirect_to ticket_path(#ticket.id)
else
flash[:error] = 'Something went wrong. Try again.'
redirect_to root_url
end
end
Problem is, whenever I run the tests, an actual ticket is created in the Zendesk backend that I'll have to delete manually later when I just want to test for successful form submission without actually creating a ticket.
So my question is, how can I test the ticket creation form without creating an actual ticket in the Zendesk backend whenever I run the tests?
The articles and blogs I've been reading as a result of my googling vaguely refers to using RackTest, while others suggest not using Capybara at all for this sort of thing, which leaves me even more confused. I'm still relatively new to RSpec and even newer to dealing with building Rails apps with an API, so a clear explanation would be great.
Thanks in advance!! You're awesome.
One way to do this would be abstract away your interface to ZenDesk into your own class and then mock it in your tests.
For example, you could create an interface class:
class ZendeskGateway
def create_ticket(client, options)
ZendeskAPI::Ticket.create(client, options)
end
end
Then, in your code, you replace the usage of the Zendesk API in your controller with your interface class:
class TicketsController < ApplicationController
attr_accessor :zendesk_gateway
after_initialize :init
def init
#zendesk_gateway = ZendeskGateway.new
end
def create
options = { subject: params[:subject], comment: { value: params[:issue] },
requester: params[:email], priority: 'normal' }
if ticket_valid? options
flash[:success] = 'Ticket created.'
#ticket = #zendesk_gateway.create_ticket(client, options)
redirect_to ticket_path(#ticket.id)
else
flash[:error] = 'Something went wrong. Try again.'
redirect_to root_url
end
end
end
Now that it is abstracted, you can using a mocking framework (like mocha) to stub out the method during your test so that it doesn't actually call out to Zendesk:
zendesk_ticket = ZendeskAPI::Ticket.new(client, :id => 1, :priority => "urgent")
#controller.zendesk_gateway.expects(:create_ticket).returns(zendesk_ticket)
This was a very quick/dirty example. But hopefully you can see the general idea.
If you don't want to call the Zendesk, you'll have to create a "test double" instead of the actual call. The test double capability that comes with RSpec is described minimally at https://github.com/rspec/rspec-mocks, but is covered more comprehensively in blogs and books.
The answer posted simultaneously to this discusses creating a separate class, but still seems to involve creating a Zendesk ticket. You don't actually need a separate class and you don't need to create any ZendeskAPI objects at all. Instead, you would stub ZendeskAPI::Ticket#create to return a test double which in turn would need to serve up whatever Zendesk ticket methods the rest of your test needs, which at least includes id.
The use of Capybara is really a secondary issue and refers to how you drive the test. But note that your test currently requires rendering the ticket page and checking the content of that page. If you want to test "just" your controller, then you can/should just test that it makes the proper calls (e.g. to ZendeskAPI::Ticket) and redirects to the appropriate page. Further, if you do just that, you have much less to simulate in your test double.

How do I re-use Capybara sessions between tests?

I want to keep on using the same session and by that I mean Rails' session between various Test::Unit integration tests that use Capybara. The Capybara::Session object is the same in all the tests as it is re-used, but when I access another page in another test, I'm immediately logged out.
Digging in I found that capybara_session.driver.browser.manage.all_cookies is cleared between one test and the next.
Any ideas how? or why? or how to avoid it?
Trying to work-around that, I saved the cookie in a class variable and re-added later by running:
capybara_session.driver.browser.manage.add_cookie(##cookie)
and it seems to work, the cookie is there, but when there's a request, the cookie gets replaced for another one, so it had no effect.
Is there any other way of achieving this?
Add the following after your capybara code that interacts with the page:
Capybara.current_session.instance_variable_set(:#touched, false)
or
page.instance_variable_set(:#touched, false)
If that doesn't work, these might help:
https://github.com/railsware/rack_session_access
http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/
If what you are doing is trying to string together individual examples into a story (cucumber style, but without cucumber), you can use a gem called rspec-steps to accomplish this. For example, normally this won't work:
describe "logging in" do
it "when I visit the sign-in page" do
visit "/login"
end
it "and I fill in my registration info and click submit" do
fill_in :username, :with => 'Foo'
fill_in :password, :with => 'foobar'
click_on "Submit"
end
it "should show a successful login" do
page.should have_content("Successfully logged in")
end
end
Because rspec rolls back all of its instance variables, sessions, cookies, etc.
If you install rspec-steps (note: currently not compatible with rspec newer than 2.9), you can replace 'describe' with 'steps' and Rspec and capybara will preserve state between the examples, allowing you to build a longer story, e.g.:
steps "logging in" do
it "when I visit the sign-in page" #... etc.
it "and I fill in" # ... etc.
it "should show a successful" # ... etc.
end
You can prevent the call to #browser.manage.delete_all_cookies that happens between tests by monkey patching the Capybara::Selenium::Driver#reset! method. It's not a clean way of doing it, but it should work...
Add the following code to your project so that it is executed after you require 'capybara':
class Capybara::Selenium::Driver < Capybara::Driver::Base
def reset!
# Use instance variable directly so we avoid starting the browser just to reset the session
if #browser
begin
##browser.manage.delete_all_cookies <= cookie deletion is commented out!
rescue Selenium::WebDriver::Error::UnhandledError => e
# delete_all_cookies fails when we've previously gone
# to about:blank, so we rescue this error and do nothing
# instead.
end
#browser.navigate.to('about:blank')
end
end
end
For interest's sake, the offending line can be seen in Capybara's codebase here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/selenium/driver.rb#L71
It may be worth posting the reason why you need this kind of behaviour. Usually, having the need to monkey patch Capybara, is an indication that you are attempting to use it for something it was not intended for. It is often possible to restructure the tests, so that you don't need the cookies persisted across integration tests.

Access the current user in Cucumber features - Devise

Note: I am very new to Cucumber.
I am trying to make a generalized step (not sure if one already exists somewhere or not) so that you can easily add objects to another object, given the association exists. I want to do something like:
manage_notes.feature
Background: Login User
Given the following user records
| email | password |
| test#email.com | password |
Given I am logged in as "test#email.com" with password "password"
Scenario: Edit existing note
Given I have already created a note that belongs to current_user
general_steps.rb
Given /^the following (.+) records?$/ do |factory, table|
table.hashes.each do |hash|
Factory(factory, hash)
end
end
Given /^I am logged in as "([^\"]*)" with password "([^\"]*)"$/ do |email, password|
unless email.blank?
visit new_user_session_path
fill_in "Email", :with => email
fill_in "Password", :with => password
click_button "Sign In"
end
end
note_steps.rb
Given /^I have already created a (.+) that belongs to (.+)$/ do |factory, resource|
model = Factory(factory)
resource.send(model.class.to_s.downcase.pluralize) << model
end
Seems like there might be a way to use the devise 'current_user' helper.
What is the correct way to accessing the user that is logged in?
Please let me know if you need more information.
Thanks!
UPDATE 1:
I have temporarily fixed my issue by creating a new step that allows me to do:
Given I have already created a note that is owned by the user with email "test#email.com"
But I don't want to specify the email, I'd still like to be able to use the logged in user if possible.
UPDATE 2:
Added general_steps.rb
So you can see, that in my 'Background', the user is created via a Factory, and then is logged in via my interface. I want to access the model of that logged in User.
I don't use Devise, so I can't answer specifically to if Devise has method of access the current_user.
But I actually like to use Pickle to help me keep my references. And perhaps this can help you out till you find a more Devise specific way to achieve what you want.
Given /^the following (.+) records$/ do |factory, table|
table.hashes.each do |hash|
Factory(factory, hash)
# since this is like an all encompassing factory creator, this line to
# create a Pickle references might need a bit more extra work if you want
# to create references for other factory types
# I assume email is unique, else use a unique identifier of some sort
find_model! %{user: "#{hash['email']}"}, {:email => hash['email']}
end
end
Given /^I have already created a (.+) that belongs to #{capture_model}$/ do |factory, name|
model = Factory(factory)
ref = model!(name) # we find that reference here
ref.send(model.class.to_s.downcase.pluralize) << model
end
This would read
Given I have already created a note that belongs to user: "test#email.com"
# I would just change this to
Given I have already created a note
# or
Given a note was created for user: "test#email.com"
You are I since you said Given I logged in..., no need to say that belongs to user: "test#email.com" it's already you.
Not to mention it could lead to confusion when you read it, some people may think you are adding a note to a user, who they might now know (or realize) is actually yourself.
While you still have to reference explicitly (eg. user: "John Doe"), I think that is a plus. By always calling specific references, everyone knows who is being referenced and there is no question about who is doing what to what.
Pickle serves us very well for this purpose. The only problematic areas we find are with things created directly through the app's ui, which gets a bit tricky to ensure you are creating the right reference to it.
Pickle has a large number of uses so definitely take a look.
Upate
You will have to find yourself here. Since, like you wanted, there is no current_user option (as far as we know). So you basically have to go find the "actor" in this scenario.
Given /^I have already created a note$/ do
user = model!(%{user: "test#email.com"}) # if using pickle
# user = User.find_by_email("test#email.com") # if just regular AR
user.notes << Note.create
end
I just solve it very simple:
I have "FactoryGirl" user defined, then this method...
Given(/^I am users logged in as "(.*?)" with password "(.*?)"$/) do |email, pass|
visit new_user_session_path
fill_in "user_email", :with => email
fill_in "user_password", :with => pass
click_button I18n.t("login.sign_in")
#current_user = User.find_by_email(email)
end
further on You could use #current_user in Your steps
This topic is quite old, but here is the solution I use. You must load this module, in env.rb, for example. Using it you can access the current user using either #current_user or current_user in your steps.
module UserSessionHelper
def current_user
#current_user
end
def login(user=nil)
if user.nil?
#current_user = FactoryGirl.create(:user, username: fake_name)
else
#current_user = user
end
visit login_path
fill_in 'Username', with: #current_user.username
fill_in 'Password', with: '1234'
click_button 'Login'
page.should have_content('Logout')
end
def logout
click_link 'Logout'
end
end
RSpec.configure do |config|
config.include UserSessionHelper
end if RSpec.respond_to?(:configure)
World(UserSessionHelper) if respond_to?(:World)
You should be able to use context for this:
http://cuke4ninja.com/sec_sharing_context.html
Have a step where you login as a user, store it in a shared context accessible to all steps and then just access it in following steps.
Hope this makes steps and I did not misinterpret the question.
Good luck with that!

How do I test the actions that require the user to be logged in?

I am trying to test my controllers, but some of the actions in my controller expect the user to be logged in. How will I test them? Do I mess with the session variable directly? Also, what if a lot of the actions expect the user to be logged in? Should I set up a before action, and log the user in there?
Another idea I had was that I could test them in an integration test, and do a post on the login form, before I actually test the desired action. Something like:
def setup
# log the user in, this will happen before every test
end
# integration test
test "I should see my posts" do
#setup should have happened before this, and I should be logged in
get posts_path
assert ...
end
Is this the way to test these actions? Am I missing something?
Depending on your authentication framework you use there are several ways. Devise for example has some TestHelpers, that make it easy to login users without having go through the actual webpage in functional tests. If thats not an option, like soundsop said, browser testing. (look at, from high to low: cucumber, capybara, selenium/...)
The Book includes some testing examples in their depot application:
test "should login" do
dave = users(:one)
post :create, :name => dave.name, :password => 'secret'
assert_redirected_to admin_url
assert_equal dave.id, session[:user_id]
end
Full details in the "Authenticating Users" section.
You can either make fake user credentials in the setup, or you can stub out the method which checks the credentials using a mocking library. I've done both and don't have a firm preference.

Resources