Integration Testing with Devise + Omniauth + Rspec + Capybara - ruby-on-rails

I am trying to write some integration tests using Rspec (feature) alongside Devise and Omniauth. The OAuth provider I am using is azure_activedirectory.
I have followed the tutorial here at the Omniauth wiki
I don't think I am doing it right. When I launch an integration test, the link for the login (localhost:3000/omniauth/azure_activedirectory) behaves like it would in dev or production, with the link directing the client to the omniauth portal page.
Of course given this is a test, I can't store credentials here.
It appears from the code in the wiki link that it should instead feed a login using a mock_auth.
Here is my current spec code:
require 'rails_helper'
Capybara.app_host = "http://lvh.me"
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:azure_activedirectory, {:uid => '12345'})
RSpec.feature "AuthenticatesUser", type: :feature do
before do
Rails.application.env_config["devise.mapping"] = Devise.mappings[:user]
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:azure_activedirectory]
end
scenario "User can login", js: true do
WebMock.allow_net_connect!
visit '/'
click_link 'login'
binding.pry
WebMock.disable_net_connect!(allow_localhost: true) # Re enable with local host
end
end
So what am I doing wrong? I feel that I am using the example code in a very incorrect way or not understanding the process.
Or is the next step just setting up a testing oauth instance and configuring it that way to use live credentials from Azure.
Thanks for the help in advance.
UPDATE
This has to do with Capybara and the JS driver starting or running on a different server.
If in the interactive ruby console in the Selenium browser OmniAuth.config.test_mode == false. If I set it using the console in the web browser, everything 'works'.

I found the solution.
You can not put OmniAuth into test mode in your specs. As capybara with js is in a separate thread, it has no idea about any config that was being set in the specs.
The solution was moving the declarations
OmniAuth.config.test_mode = true
OmniAuth.config.auth_mocks = NewAuthMock
into the test.rb environment file, that way Capybara would spawn with the correct config.

Related

Capybara / Poltergeist / Devise #current_user is nil - Rails 4

I got a feature spec which receives a token from an external site and redirects back to my app. Upon redirecting, the client is served an orders#new view, which calls #current_user.items. This call works in all environments except test. Capybara / Poltergeist or Selenium get undefined method items for nil class
I've tried:
assigning #current_user in before(:each)
changing the web driver
setting #current_user to User.first for Rails.env.test?in my
controller
How can I make sure #current_user works in test?
You need to either login in your feature spec, or use wardens test mode through devise to fake the login - see https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara - so that the current user is set (along with the relevant session cookies). Feature tests and the app each run separately when using any Capybara driver other than rack-test so attempting to set #current_user in a before(:each) isn't going to work. Setting it in the controller would probably work for that controller action but won't set the session cookies if you're following links, etc - and doing something like that in a controller just for testing is generally a bad idea.
Tom's answer made me realize that cookies are not stored because Capybara hosts are not static. I ended up explicitly setting the server_port, server_host and app_host in the rails_helper which worked like a charm:
Capybara.app_host = 'http://localhost:3000'
Capybara.server_host = 'localhost'
Capybara.server_port = '3000'
My app is not using Devise and Warden. Otherwise, Tom's answer would've worked.

Is it possible to call GET in a Capybara/Rspec integration test?

I have a Rails 4.2 application....I was adding content compression via this thoughtbot blog post, but I get an error such as:
undefined method `get' for #<RSpec::ExampleGroups::Compression:0x00000009aa4cc8>
Perusing over the capybara docs, it seems like you shouldn't be using get. Any idea how to test the below then in Rails 4?
# spec/integration/compression_spec.rb
require 'spec_helper'
feature 'Compression' do
scenario "a visitor has a browser that supports compression" do
['deflate','gzip', 'deflate,gzip','gzip,deflate'].each do|compression_method|
get root_path, {}, {'HTTP_ACCEPT_ENCODING' => compression_method }
response.headers['Content-Encoding'].should be
end
end
scenario "a visitor's browser does not support compression" do
get root_path
response.headers['Content-Encoding'].should_not be
end
end
In a capybara test you would use visit not get (as described here), but that answer won't actually help you because the test you've written above is not an integration test, it's a controller test.
Move it to spec/controllers and use the controller-specific helpers describe/context/it etc. to construct your tests for your controller. You can set the headers and do the sorts of checks that you're doing in the code you're showing.

using VCR with Rspec in feature scenarios

I have a Rails 4 app that uses a custom authentication gem that authenticates users against a third-party API. The app requires authentication for most actions on the site (visitors can do very little).
I am trying to use VCR to record the api request made during authentication for all of the integration tests, but all examples that I can find on SO and the Relish documentation only cover how to do this with Rspec in a 'describe do' spec, as referenced here:
https://www.relishapp.com/vcr/vcr/v/1-6-0/docs/test-frameworks/usage-with-rspec
Since no customers are involved on this project, I am writing integration tests with Rspec and Capybara instead of Cucumber, so my tests are using the 'feature/scenario' format like so:
feature 'posts' do
scenario 'a user can log in' do
# use vcr for api request
sign_in_user # refers to a method that handles the api call to log in a user, which is what I would like VCR to record.
expect(page).to have_content("User signed in successfully")
end
end
Using the command described in the documentation:
use_vcr_cassette
inside of the 'scenario' block, returns an error:
Failure/Error: use_vcr_cassette
undefined local variable or method `use_vcr_cassette' for #<RSpec::ExampleGroups::Posts:0x007fb858369c38>
I followed the documentation to setup VCR in my spec/rails_helper.rb (which is included by the spec/spec_helper.rb)... which basically looks like this:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'support/vcr_cassettes'
c.hook_into :webmock
end
Obviously added gem 'vcr' to my Gemfile development/test group and it is a thing in console and binding.pry from inside of a test.
Has anyone used VCR inside of a Rspec feature? or have any suggestions on what I might do as a workaround?
Thanks in advance
Solution: Taryn East got me to the solution, but it is slightly different than the link posted for anyone trying to do this moving forward.
here is the most basic config in spec/rails_helper.rb or spec/spec_helper.rb:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'spec/cassettes'
c.hook_into :webmock
c.configure_rspec_metadata!
end
using c.configure_rspec_metadata! is required for Rspec to handle the :vcr tag.
And in an Rspec Feature spec:
feature 'users' do
scenario 'logged in users should be able to do stuff', :vcr do
# authenticate user or make other http request here
end
end
Oddly enough, in my tests - VCR is recording the response and if passes the first time, but fails the second time. I traced this to the response being stored differently than it is received.
On a normal request (using excon) like so:
resp = Excon.post(url, :body => data, :headers => { "Content-Type" => "application/x-www-form-urlencoded", "Authorization" => authorization_header })
The response has a header that is accessible in this format:
resp.headers["oauth_token"]
which returns an oauth token.
In the VCR response, it is being stored differently and only accessible as:
resp.headers["Oauth-Token"]
Which is weird, but workable. This may be a bug with VCR or some issue with Excon... too busy to figure that one out right now, but just a heads up in case anyone else uses this setup and gets a passing test with the live http request and a failing test when using the VCR cassette. A quick workaround is to either change the VCR cassette data to match what your code expects, or modify your code to accept either available value.

Testing facebook login with rails cucumber

I have set up Facebook login on my site following this tutorial and am using cucumber and capybara. I have tried following other SO posts like this that explain how to set up a fake login account. If I use this directly, I get:
When I follow "sign_in" # features/step_definitions/web_steps.rb:56
No route matches [GET] "/oauth/authorize" (ActionController::RoutingError)
./features/step_definitions/web_steps.rb:57:in `/^(?:|I )follow "([^"]*)"$/'
features/facebook_signin.feature:9:in `When I follow "sign_in"'
If I add get "/oauth/authorize" to my routes, I get:
When I follow "sign_in" # features/step_definitions/web_steps.rb:56
uninitialized constant OauthController (ActionController::RoutingError)
./features/step_definitions/web_steps.rb:57:in `/^(?:|I )follow "([^"]*)"$/'
features/facebook_signin.feature:9:in `When I follow "sign_in"'
I don't know what is going on and why it is complaining. If I change my Gemfile from gem 'omniauth-facebook', '1.4.0' to just gem 'omniauth-facebook' I get virtually the same errors above except instead of:
/oauth/authorize, I get /dialog/oauth and instead of uninitialized constant OauthController, I get uninitialized constant DialogController
Has anyone recently successfully set up cucumber testing for login with Facebook?
When I am on localhost:3000 and navigate to localhost:3000/auth/facebook everything works and I am using a sessionsController so I don't understand why in testing, it is trying to use these oauthControllers or DialogueControllers.
I recently had the same issue or a very similar issue:
ActionController::RoutingError:
No route matches [GET] "/dialog/oauth"
I had taken the working specs which properly set up mock responses from another project and was pretty surprised to suddenly get this error.
After much pain and suffering I had a major facepalm moment when I realized i had forgot to set OmniAuth to use test mode:
# spec/rails_helper.rb
OmniAuth.config.test_mode = true
This will cause OmniAuth to short circuit so that you can set the auth responses by:
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
:provider => 'facebook',
:uid => '123545'
# etc.
})
I think the error is due to OmniAuth trying to be helpful by using the :developer strategy in the test environment so that you don't get banned by the auth provider.
See https://github.com/intridea/omniauth/wiki/Integration-Testing.
Have you enabled Javascript in this tests? As the tutorial mentions the facebook.js.coffee?
describe 'When I follow "sign in"', js: true do
Another possibility is that you included the gem only in the development block of the Gemfile
group :development do
gem 'omniauth-facebook'
end
BTW: you shouldn't be testing against "real" external endpoints. Take a look at webmock for this kind of tests to mock the Facebook response
I had to put #omniauth_test_success before the feature and not the step definition. Additionally, I had to fix some routing issues. Will post full report later this week.

Why Do Rails Tests Run as www.example.com [duplicate]

I have a rails application which acts differently depending on what domain it's accessed at (for example www.myapp.com will invoke differently to user.myapp.com). In production use this all works fine but my test code always sees a hostname of "www.example.com".
Is there a clean way of having a test specify the hostname it's pretending to access?
Integration/Request Specs (inheriting from ActionDispatch::IntegrationTest):
host! 'my.awesome.host'
See the docs, section 5.1 Helpers Available for Integration Tests.
alternatively, configure it globally for request specs at spec_helper.rb level:
RSpec.configure do |config|
config.before(:each, type: :request) do
host! 'my.awesome.host'
end
end
Controller Specs (inheriting from ActionController::TestCase)
#request.host = 'my.awesome.host'
See the docs, section 4.4 Instance Variables Available.
Feature Specs (through Capybara)
Capybara.default_host = 'http://my.awesome.host'
# Or to configure domain for route helpers:
default_url_options[:host] = 'my.awesome.host'
From #AminAriana's answer
View Specs (inheriting from ActionView::TestCase)
#request.host = 'my.awesome.host'
...or through RSpec:
controller.request.host = 'my.awesome.host'
See the rspec-rails view spec docs.
#request.host = 'user.myapp.com'
Feature specs
In Feature specs, host! has been deprecated. Add these to your spec_helper.rb:
# Configure Capybara expected host
Capybara.app_host = "http://test.domain"
# Configure actual routes host during test
before(:each) do
default_url_options[:host] = <myhost>
end
Request specs
In Request specs, keep using host! :
host! "test.domain"
Alternatively refactor it in before(:each) blocks, or configure it globally for request specs at spec_helper.rb level:
RSpec.configure do |config|
config.before(:each, type: :request) do
host! "test.domain"
end
end
For Rspec Request specs, use before(:each) { host! 'example.com' }
See more at:
https://relishapp.com/rspec/rspec-rails/v/3-6/docs/request-specs/request-spec
https://github.com/rspec/rspec-rails/issues/1662#issuecomment-241201056
I believe you can modify the HTTP_HOST or SERVER_NAME environment vars to change the request that goes to the router:
ENV['SERVER_NAME'] = "user.myapp.com"
See raw_host_with_port in actionpack/lib/action_controller/request.rb.
Another thing to remember is to make sure to use the correct session instance so that you can properly encapsulate the url helpers.
Integration tests provide you with a default session. You can call all session methods directly from your tests
test "should integrate well" do
https!
get users_path
assert_response :success
end
All these helpers are using the default session instance, which if not changed, goes to "www.example.com". As has been mentioned the host can be changed by doing host!("my.new.host")
If you create multiple sessions using the open_session method, you must ALWAYS use that instance to call the helper methods. This will properly encapsulate the request. Otherwise rails will call the default session instance which may use a different host:
test "should integrate well" do
sess = open_session
sess.host! "my.awesome.host"
sess.get users_url #=> WRONG! will use default session object to build url.
sess.get sess.users_url #=> Correctly invoking url writer from my custom session with new host.
sess.assert_response :success
end
If you intended to use the default session object, then you'll have to alter that host as well:
test "should integrate well" do
sess = open_session
sess.host! "my.awesome.host"
host! sess.host #=> Set default session host to my custom session host.
sess.get users_url
end
#request.host = 'user.myapp.com' is not right.
should use host!('user.myapp.com')
I tried many variations of #request.host, host!, and post path, args, {'SERVER_NAME' => my_secret_domain} without success, both as controller tests and feature tests. Very aggravating, as so many others reported success with those approaches.
The solution for me was:
request.headers["SERVER_NAME"] = my_secret_domain
post path, args
I'm running ruby 2.1.5p273, rspec 3.1.7 and Rails 4.2.0
None of the ways suggested in other answers at the point worked for me. This worked:
Capybara.configure { |config| config.default_host = "my.domain.com" }
Yet another answer:
request.host = "user.myapp.com"
I know it resembles the correct answer, but please bear with me. I don't like assignment operation in test just to set things up, I'd prefer an explicit stub. Interestingly, stubbing like this won't work:
allow(request).to receive(:host).and_return("user.myapp.com")
I personally prefer stubbing over assignment, that way I get 2 benefit, one is that it will be validated by rspec's verify double, second is that it is explicitly saying that is a stub, not part of the test excercise.

Resources