RSpec. Rails 3.2. Instance variable checking. Behavior testing - ruby-on-rails

I have a question with RSpec testing. I am using FactoryGirl, Capybara 2.* and trying to testing my website behavior.
Scenario of testing:
User clicking on sign_in button (devise controller), he is redirected to root_path (managed by MyController). After his redirection before_filter :setup_params should assign to #app variable (in action :find_apps in MyController) some values. I would like to ensure that #app is not nil and that values were assigned.
here is my sign_in_spec.rb
require "spec_helper.rb"
require "mymodel.rb"
describe MyContoller, :type => :feature do
before do
visit '/users/sign_in'
end
it "Shall redirect to user and ensure that #app is not nil" do
user = FactoryGirl.build(:user_monit)
fill_in "user[email]", with: user.email
fill_in "user[password]", with: user.password
#expect {click_button "Sign in"}.to change {#myapp}.from(nil)
click_button "Sign in"
get :find_apps
assigns(:myapp).should_not be_nil
end
end
require section in my spec_helper.rb
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rails'
require 'capybara/rspec'
require 'mocha/setup'
require 'factory_girl'
....
config.include RSpec::Rails::RequestExampleGroup, type: :feature
I have got several mistakes:
1) result should have changed, but is still nil
2) bad argument error for get :find_apps (ArgumentError: bad argument (expected URI object or URI string))
What am I doing wrong?
Thanks in advance!

You have to create User before you can login so :
user = FactoryGirl.create(:user_monit)
second issue you can resolve adding normal uri instead :find_apps the same like you make in before block

Related

RSpec/Capybara does not commit to DB within test

I'm quite desperate since moving our test suite from Minitest to RSpec. All the controller and model tests run fine so far, but since trying to port (formerly passing/working) feature tests like the following I ran into trouble...
feature 'Create place' do
scenario 'create valid place as user' do
login_as_user
visit '/places/new'
fill_in_valid_place_information
click_button('Create Place')
visit '/places'
expect(page).to have_content('Any place', count: 1)
end
...
def fill_in_valid_place_information
fill_in('place_name', with: 'Any place')
fill_in('place_street', with: 'Magdalenenstr.')
fill_in('place_house_number', with: '19')
fill_in('place_postal_code', with: '10963')
fill_in('place_city', with: 'Berlin')
fill_in('place_email', with: 'schnipp#schnapp.com')
fill_in('place_homepage', with: 'http://schnapp.com')
fill_in('place_phone', with: '03081763253')
end
end
Unfortunately this does not lead to a DB commit which makes the test fail. It does not fail if i pry into the test and manually create the requested place. I tried different methods in order to trigger the button but nothing worked so far.
This is how my rails_helper.rb looks like:
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'capybara/rspec'
require 'capybara/rails'
require 'capybara/poltergeist'
require 'pry'
def validate_captcha
fill_in 'captcha', with: SimpleCaptcha::SimpleCaptchaData.first.value
end
def login_as_user
user = create :user, email: 'user#example.com'
visit 'login/'
fill_in 'sessions_email', with: 'user#example.com'
fill_in 'sessions_password', with: 'secret'
click_on 'Login'
end
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
ActiveRecord::Migration.maintain_test_schema!
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, phantomjs_options: ['--ignore-ssl-errors=true'])
end
Capybara.javascript_driver = :poltergeist
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.clean
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, no_transaction: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
config.include Capybara::DSL
config.include Rails.application.routes.url_helpers
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
Does anyone have a clue about a possible cause? Gem versions:
capybara 2.12.0
rspec 3.5.0
rails 4.2.7.1
best and thanks,
Andi
--- Update
I added fill_in_valid_place_information method
This is how the test fails with or without a Capybara JS driver enabled (shouldn't matter in case of this test as the feature does not use any JS). Unfortunately it doesn't give any real hints to work with...
1) Create place create valid place as user
Failure/Error: expect(page).to have_content('Any place', count: 1)
expected to find text "Any place" 1 time but found 0 times in "KIEZ KARTE Find places Here comes a list of all POIs currently available in our database. If you are looking for a specific location please enter parts of its descriptive features into the 'Search' field. Search: Name Postal code Categories No data available in table"
Timeout reached while running a *waiting* Capybara finder...perhaps you wanted to return immediately? Use a non-waiting Capybara finder. More info: http://blog.codeship.com/faster-rails-tests?utm_source=gem_exception
--- Update 2
I found the issue which is not capyara-related. Actually I forgot to transfer a stub response for an API we're calling. Thanks everybody for participating in my struggle!
There are a number of potential issues in your test that could be causing what you are seeing, it would be easier to narrow down in the future if you included the actual error message(s) your test produces.
Your scenarion/feature isn't tagged with :js metadata to activate using the Capybara driver. It's possible you've specified Capybara.default_driver somewhere, but if so then your DatabaseCleaner config is wrong
Use the recommended DatabaseCleaner configuration from https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example . The driver name detection will work if you have specified Capybara.default_driver as mentioned in #1 and also with the :js/:driver metadata usage pattern. Additionally, the append_after/after difference is important to reduce test flakiness
Your login_as_user method needs to verify the login has completed before returning. This is because click_on 'Login' can trigger asynchronously and return before the login actually occurs. This leads to the visit you call immediately following aborting the login, preventing the session cookie from being sent, and ending up with a non logged in user when you expected the user to be logged in. To fix this you need something like
def login_as_user
...
click_on 'Login'
expect(page).to have_text('You are now logged in!') #whatever message is shown on successful login, or use have_css with some element on the page that only exists when a user is logged in (user menu, etc)
end
The same issue exists between click_button('Create Place') and visit '/places' where the visit can effectively cancel the effects of the button click

Can't sign in with FactoryGirl's user/data

I have to sign in the user before doing my tests which will use JS. The sign in page does work (except on this test).
require 'spec_helper'
require 'capybara/poltergeist'
include Capybara::DSL
Capybara.javascript_driver = :poltergeist
describe "Application", js: true do
describe "when logged in", js: true do
let(:user) { FactoryGirl.create(:user) }
before do
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
it "can authenticate user" do
User.all.first.authenticate(user.password).email.should == user.email
end
end
end
The problem is that clicking the "Sign in" button returns back to the page with "invalid user/password" combination (as if the user is not in the db). The thing is that apparently the use really IS in the database and the password is right (the test "can authenticate user" passes).
I took a screenshot before clicking the "Sign in" button and the fields are populated correctly. I tried to create the user inside the before block but it didn't work either.
The sign in page doesn't have anything special, no javascript or anything. Just a normal form post to the server.
Any ideas?
this was happening to me a while ago while using rspec and poltergeist. The problem was that phantomjs thread wasn't able to see the record created by FactoryGirl in the db. To solve this problem, you have to configure your db connection to be shared in your spec_helper.rb adding the code below.
class ActiveRecord::Base
mattr_accessor :shared_connection
##shared_connection = nil
def self.connection
##shared_connection || retrieve_connection
end
end
# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
You can find more explanation here in the point number 3.

Rails: RSpec - undefined method `cookie_jar' for nil:NilClass

Rails newbie. Trying to follow Michael Hartl's tutorial.
Stuck trying to add a helper method to simulate log in an RSpec test:
describe "when the a user has logged in and attempts to visit the page" do
let(:user) { FactoryGirl.create :user }
before do
log_in user
end
it "should redirect the user to next page" do
specify { response.should redirect_to loggedin_path }
end
end
In my spec/support/utilities.rb:
def log_in user
visit root_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
cookies[:remember_token] = user.remember_token
end
Error:
Failure/Error: log_in user
NoMethodError:
undefined method `cookie_jar' for nil:NilClass
What gives?
Edit, full stack trace:
Index page when the a user has logged in and attempts to visit the page should redirect the user to next page
Failure/Error: log_in user
NoMethodError:
undefined method `cookie_jar' for nil:NilClass
# ./spec/support/utilities.rb:8:in `log_in'
# ./spec/features/pages/index_spec.rb:20:in `block (3 levels) in <top (required)>'
RSpec is very particular about the directory that you place your tests. If you put the test in the wrong directory, it won't automagically mix-in the various test helpers that setup different types of tests. It seems your setup is using spec/features which is not an approved default directory (spec/requests, spec/integration, or spec/api).
Based on the tutorial page, I'm not sure how they have the spec_helper.rb file setup. Though the examples so they are using spec/requests to hold the tests.
You can force RSpec to recognize another directory for request specs by using on of the following:
Manually add the proper module to the test file:
# spec/features/pages/index_spec.rb
require 'spec_helper'
describe "Visiting the index page" do
include RSpec::Rails::RequestExampleGroup
# Rest of your test code
context "when the a user has logged in and attempts to visit the page" do
let(:user) { FactoryGirl.create :user }
before do
log_in user
end
specify { response.should redirect_to loggedin_path }
end
end
Include this in your spec/spec_helper.rb file:
RSpec::configure do |c|
c.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
file_path: c.escaped_path(%w[spec (features)])
}
end
Since this is a tutorial I'd recommend following the standard of including require 'spec_helper' at the top of the spec file and that your actual spec/spec_helper.rb file has require 'rspec/rails'
A minor note, you don't need to put a specify inside of an it block. They are aliases of each other, so just use one.
context "when the a user has logged in and attempts to visit the page" do
let(:user) { FactoryGirl.create :user }
before do
log_in user
end
# All of the following are the same
it "redirects the user to next page" do
response.should redirect_to loggedin_path
end
it { response.should redirect_to loggedin_path }
specify "redirects the user to next page" do
response.should redirect_to loggedin_path
end
specify { response.should redirect_to loggedin_path }
end
Note, according to the documentation for capybara, you should be able to put your capybara tests into spec/features. To make this work, ensure you are loading require 'capybara/rspec' in your spec_helper or test spec file directly.
However, looking at the source, I didn't see where they are automatically including that directory. You can also try adding the tag type: :feature to the outer describe block in your test file. Though the more likely solution is to use spec/requests.
Shouldn't you have the "user" argument of the method enclosed in parenthesis?
def log_in(user)
visit root_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
cookies[:remember_token] = user.remember_token
end
To have a mock cookie jar, you must have either rack-test or rspec-rails gem included in your Gemfile. I think maybe you have included just rspec and maybe missed out rspec-rails.
You also need to ensure you've configured the session store as follows:
config.session_store = :cookie_store
This must have been done in either config/application.rb or some file under config/initializers. If you have configured this in config/environments/development.rb or somewhere else, the Test environment will not be able to pick it up.

Using Cucumber, is there a way to log a user in without the interface?

The vast majority of my cucumber features require the user to be logged in. However I don't really need to test the login functionality for every single test. I'm currently using Devise for my authentication.
I'm looking for a way to sign a user in with devise, without filling out the sign in form. Is there anyway to do this? I would prefer to not have to use the sign in action for every test.
No, there is no way. In the documentation, with regard to the sign_in #user and sign_out #user helper methods, it says:
These helpers are not going to work for integration tests driven by Capybara or Webrat. They are meant to be used with functional tests only. Instead, fill in the form or explicitly set the user in session
As you said yourself, it is probably cleanest to do it with a before :each block. I like to structure it like the following:
context "login necessary" do
# Before block
before do
visit new_user_session_path
fill_in "Email", with: "test#test.com"
fill_in "Password", with: "password"
click_button "Login"
assert_contain "You logged in successfully."
end
# Actual tests that require the user to be logged in
it "does everything correctly" do
# ...
end
end
context "login not necessary" do
it "does stuff" do
# code
end
end
I found this to be quite useful, since if I change authentication rules (i.e. whether or not the user has to be logged in for a specific path) I can just take the whole test and move it into the other description block, without changing any more code.
Generally, you should always test through the interface. But I think this is an acceptable exception.
I'm using devise with capybara with rspec but it should work for you too.
In a helper I have this:
module LoginHelper
def login_as(user)
super(user, :scope => :user, :run_callbacks => false)
end
end
RSpec.configure do |config|
config.include Warden::Test::Helpers, :type => :feature
config.include LoginHelper, :type => :feature
config.before :each, :type => :feature do
Warden.test_mode!
end
config.after :each, :type => :feature do
Warden.test_reset!
end
end
Then in the feature:
background do
login_as(user)
visit root_path
end
Also see:
How to Stub out Warden/Devise with Rspec in Capybara test

Devise and OmniAuth twitter integration testing with rspec

I am trying to write a integration test for signing in with twitter using OmniAuth and Devise. I am having trouble getting the request variable to be set. It works in the controller test but not the integration test which leads me to think that I am not configuring the spec helper properly. I have looked around, but I can't seem to find a working solution. Here is what I have so far:
# spec/integrations/session_spec.rb
require 'spec_helper'
describe "signing in" do
before do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
visit new_user_session_path
click_link "Sign in with twitter"
end
it "should sign in the user with the authentication" do
(1+1).should == 3
end
end
This spec raies a error before it can get to the test and I am not quite sure where the request variable needs to be initialized. The error is:
Failure/Error: request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
NoMethodError:
undefined method `env' for nil:NilClass
Now I use the request variable in my controller spec and the test pass but it is not being initialized for the integration tests.
# spec/spec_helper.rb
Dir[Rails.root.join("spec/support/*.rb")].each {|f| require f}
...
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Thanks for the help!
Capybara README says "Access to session and request is not possible from the test", so I gave up to configure in test and decided to write a helper method in application_controller.rb.
before_filter :set_request_env
def set_request_env
if ENV["RAILS_ENV"] == 'test'
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
end
The Devise test helpers are only meant to be used in controller specs not integration specs. In capybara there is no request object so setting it won't work.
What you should do instead is scope loading of Devise test helpers to your controller specs, something like this:
class ActionController::TestCase
include Devise::TestHelpers
end
and use the warden helper for capybara specs as suggested in this guide: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
For a more detailed discussion look at this github issue page: https://github.com/nbudin/devise_cas_authenticatable/issues/36
This one works for me during test using rspec + devise + omniauth + omniauth-google-apps. No doubt the twitter solution will be very similar:
# use this method in request specs to sign in as the given user.
def login(user)
OmniAuth.config.test_mode = true
hash = OmniAuth::AuthHash.new
hash[:info] = {email: user.email, name: user.name}
OmniAuth.config.mock_auth[:google_apps] = hash
visit new_user_session_path
click_link "Sign in with Google Apps"
end
When using request specs with newer versions of RSpec, which do not allow access to the request object:
before do
Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] # If using Devise
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end

Resources