I am trying to write controller / integration tests for my Shopify application built with Rails 5 and the shopify_app gem.
The problem I am facing is I can't find out how to bypass the authentication process in my test environment. I can't find the correct method to stub.
What I tried:
#test_helper.rb
class ActiveSupport::TestCase
# Turn on "test mode" for OmniAuth
OmniAuth.config.test_mode = true
end
.
#products_controller.test.rb
class ProductsControllerTest < ActionDispatch::IntegrationTest
def setup
# Add OAuth mock provider
# Sets authentication hash to return during integration testing
#shop = build(:shop)
OmniAuth.config.add_mock(
:shopify,
prodiver: :shopify,
uid: #shop.shopify_domain,
credentials: {
token: #shop.shopify_token
}
# This get request gets redirected to http://www.example.com/
get shopify_app.auth_shopify_callback_url, params: {shop: #shop.shopify_domain}
end
test "should get index" do
get root_url, { env: {
"omniauth.auth" => OmniAuth.config.mock_auth[:shopify],
"omniauth.params" => { shop: #shop.shopify_domain }
}}
assert_response :success
end
end
Test result:
Failure:
ProductsControllerTest#test_should_get_index [/shopify_stock_exporter/test/controllers/products_controller_test.rb:7]:
Expected response to be a <2XX: success>, but was a <302: Found> redirect to http://www.example.com/login
Questions:
is this the correct way to set the request's env variable?
what should the mock return to simulate a successful authentication?
Related resources:
mock_shopify_omniauth method from the shopify_app gem
Omniauth wiki
Since shopify_app uses omniauth for authentication, you should check out the omniauth wiki: https://github.com/omniauth/omniauth/wiki/Integration-Testing
You'll need to set OmniAuth.config.test_mode = true and then use OmniAuth.config.mock_auth.
Hope this helps!
Related
I have the following test to ensure sign in is required. Most of the tests require a signed in user, so I have the user sign in on setup. However for this test, I need them signed out.
require 'test_helper'
class DealsControllerTest < ActionDispatch::IntegrationTest
include Warden::Test::Helpers
setup do
#deal = deals(:one)
#user = users(:one)
# https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
#user.confirmed_at = Time.now
#user.save
login_as(#user, :scope => :user)
end
teardown do
Warden.test_reset!
end
test "require sign in for deal list" do
logout #user
get deals_url
assert_redirected_to new_user_session_path
end
I get the error
Failure:
DealsControllerTest#test_require_sign_in_for_deal_list [C:/Users/Chloe/workspace/fortuneempire/test/controllers/deals_controller_test.rb:35]:
Expected response to be a <3XX: redirect>, but was a <200: OK>
It says it will work, but it's just not working.
https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
If for some reason you need to log out a logged in test user, you can use Warden's logout helper.
logout(:user)
Rails 5.0.2
Devise 4.2.1
logout by itself without any parameters worked. Maybe logout :user would work too. I wasn't using FactoryGirl so was confusing it for a FactoryGirl symbol.
This is an adopted rails app with no tests. I am trying to test omniauth in an integration test but am getting an error (edit I have based upon this: https://github.com/intridea/omniauth/wiki/Integration-Testing). This reflects my lack of understanding of Rspec. It would seem that the request object would be available by default.
I have in my spec/spec_helper.rb:
config.include IntegrationSpecHelper, :type => :request
Capybara.default_host = 'http://localhost:3000'
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:facebook, {
:uid => '12345'
})
and in my spec/integration/login_spec:
require 'spec_helper'
describe ServicesController, "OmniAuth" do
before do
puts OmniAuth.config.mock_auth[:facebook]
puts request # comes back blank
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
end
it "sets a session variable to the OmniAuth auth hash" do
request.env["omniauth.auth"][:uid].should == '12345'
end
end
and I get the following error:
{"provider"=>"facebook", "uid"=>"12345", "user_info"=>{"name"=>"Bob
Example"}}
F
Failures:
1) ServicesController OmniAuth sets a session variable to the
OmniAuth auth hash
Failure/Error: request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
NoMethodError:
undefined method env' for nil:NilClass
# ./login_spec.rb:8:inblock (2 levels) in '
Finished in 22.06 seconds 1 example, 1 failure
Failed examples:
rspec ./login_spec.rb:11 # ServicesController OmniAuth sets a session
variable to the OmniAuth auth hash
Should the request object be available here, by default? Does this error possibly mean something else?
thx
You're getting nil because you haven't made any request yet.
To make the test work, you have to do three things:
Set up the mock
Make the request
Test whatever code is attached to the callback
Here's how I do it. First set up the mock in the before block, and then visit the URL corresponding to the provider (in this case facebook):
before do
OmniAuth.config.add_mock(:facebook, {:uid => '12345'})
visit '/auth/facebook'
end
From the wiki:
A request to /auth/provider will redirect immediately to /auth/provider/callback.
So you have to have a route which matches '/auth/:provider/callback'. Whatever action you map that do has to perform the stuff in step 3 above.
If you wanted to test that the session variable was set to the uid, you could do something like this (which works because you set the uid to '12345' in the mock above):
it "sets a session variable to the OmniAuth auth hash" do
session['uid'].should == '12345'
end
And here's a route and action that should make this pass:
routes.rb
match '/auth/:provider/callback' => 'sessions#callback'
controllers/sessions_controller.rb
def callback
session['uid'] = request.env["omniauth.auth"][:uid]
end
That's the gist of it. Hope that helps.
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
We are starting to develop a new website using Rails 3, RSpec 2, and OmniAuth 2. We wanted to follow TDD using RSpec to write the authentication, but we actually don't know where to start. We don't want to test the gem, as it had already been tested, but we want to test the flow of the application and it has routed correctly according to the outcome of the callback.
The best so far is we categorized the problem in two stages:
1- Before the callback: Faking the services like facebook and twitter to return the calls
2- After the callback: getting the results and creating the user and related service
Please guide us and shed us some light :)
Did you checkout the Wiki?
** Edit **
Perhaps something like (untested):
before do
OmniAuth.config.mock_auth[:twitter] = {
'uid' => '123545'
}
end
it "sets a session variable to the OmniAuth auth hash" do
controller.session[:auth_hash]['uid'].should == '123545'
end
I have experiencing problem for writhing RSpec for OmniauthCallbacksController, do some research on this and it working for me. I am here adding my codes, if anyone found necessary. The tests are for happy path :)
require 'spec_helper'
describe OmniauthCallbacksController do
describe "#linkedin" do
let(:current_user) { Fabricate(:user) }
before(:each) do
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:linkedin] = OmniAuth::AuthHash.new({provider: :linkedin, uid: '12345', credentials: {token: 'linkedin-token', secret: 'linkedin-secret'}})
request.env["devise.mapping"] = Devise.mappings[:user]
#controller.stub!(:env).and_return({"omniauth.auth" => OmniAuth.config.mock_auth[:linkedin]})
User.stub(:from_auth).and_return(current_user)
end
describe "#linkedin" do
context "with a new linkedin user" do
before { get :linkedin }
it "should authenticate user" do
warden.authenticated?(:user).should == true
end
it "should set current_user" do
subject.current_user.should_not be_nil
end
it "should redirect to root_path" do
response.should redirect_to(root_path)
end
end
end
end
end
I'm writing some RSpec tests for my Rails 3 application and trying to switch from Webrat to Capybara. So far so good but the application uses HTTP basic auth to authorize my admin user, any idea how I can test that with Capybara?
Here is my current Webrat step:
it 'should authenticate for admin' do
basic_auth('user', 'secret')
visit '/admin'
response.status.should eql 200
response.status.should_not eql 401
end
How do I do this with Capybara? Thanks!
I got it to work using page.driver.basic_authorize(name, password) instead
Update:
At the moment, after a Capybara upgrade, I'm using this pile of workarounds:
if page.driver.respond_to?(:basic_auth)
page.driver.basic_auth(name, password)
elsif page.driver.respond_to?(:basic_authorize)
page.driver.basic_authorize(name, password)
elsif page.driver.respond_to?(:browser) && page.driver.browser.respond_to?(:basic_authorize)
page.driver.browser.basic_authorize(name, password)
else
raise "I don't know how to log in!"
end
The default Capybara driver, rack-test, has a basic_authorize method (with alias authorize) for Basic HTTP Auth, and digest_authorize for Digest HTTP Auth, here you can find them: https://github.com/brynary/rack-test/blob/master/lib/rack/test.rb
So you can do:
page.driver.browser.authorize 'login', 'password'
Or you can write a simple helper for Basic HTTP Auth:
def basic_auth(user, password)
encoded_login = ["#{user}:#{password}"].pack("m*")
page.driver.header 'Authorization', "Basic #{encoded_login}"
end
None of the page.driver.* solutions worked for me. I'm using Poltergeist, not Selenium, so that might have something to do with it. Here's what did work:
RSpec.shared_context "When authenticated" do
before do
username = 'yourusername'
password = 'yourpassword'
visit "http://#{username}:#{password}##{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/"
end
end
Then, in your spec:
feature "Your feature", js: true do
include_context "When authenticated"
# Your test code here...
end
This has changed in recent versions of cucumber-rails (I am using 1.0.2).
cucumber-rails uses the Rack/Test driver by default, so if you have not changed that, the following instructions will work.
Create features/step_definitions/authorize.rb:
Given /^I am logged in as "([^\"]*)" with "([^\"]*)"$/ do |username, password|
authorize username, password
end
Now you can use this in your features:
Given I am logged in as "admin" with "password"
I had to do this horrible hack to get it work worth headless and with javascript
Given /^I am logged in$/ do
if page.driver.respond_to?(:basic_authorize)
page.driver.basic_authorize('admin', 'password')
else
# FIXME for this to work you need to add pref("network.http.phishy-userpass-length", 255); to /Applications/Firefox.app/Contents/MacOS/defaults/pref/firefox.js
page.driver.visit('/')
page.driver.visit("http://admin:password##{page.driver.current_url.gsub(/^http\:\/\//, '')}")
end
end
Man, none of these solutions worked for me.
Pistos' solution came close and worked for feature specs with js: true but failed when headless.
This below solution works for me for both headless and js: true specs.
spec/support/when_authenticated.rb
RSpec.shared_context 'When authenticated' do
background do
authenticate
end
def authenticate
if page.driver.browser.respond_to?(:authorize)
# When headless
page.driver.browser.authorize(username, password)
else
# When javascript test
visit "http://#{username}:#{password}##{host}:#{port}/"
end
end
def username
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_username
end
def password
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_password
end
def host
Capybara.current_session.server.host
end
def port
Capybara.current_session.server.port
end
end
Then, in your spec:
feature 'User does something' do
include_context 'When authenticated'
# test examples
end