Rails noob here. I'm having trouble understanding how/what to test for regarding authentication with Omniauth-Facebook. Pretty much have a similar setup to the relevant railscast. I have my test environment set up similarly to what's described in previous questions and on the Gem wiki.
Couple of questions. When you create a User factory how do you get resulting object to mock an authentication?
Also what goes on when the following code is run?
before do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
visit '/auth/facebook'
end
Is an object saved in a database?
I've added some sample specs below and the Factory spec. Again the spec_helper setup file has the test mode config set to true.
The add_mock setup:
OmniAuth.config.add_mock(:facebook,
{ :provider => 'facebook',
:uid => '1234567',
:info => { :name => 'Jonathan', :image => 'http://graph.facebook.com/1234567/picture?type=square'},
:credentials => {
:expires_at => 1351270850,
:token=> 'AAADzk0b791YBAHCNhBI3n6ScmWvuXTY4yIUqXr9WiZCg1R808RYzaHxsHnrbn62IwrIgZCfSBZAVIP6ptF41nm8YtRtZCeBbxbbz1mF8RQZDZD'
} })
User_pages_spec:
describe "user pages" do
let(:user) { Factory.create(:user) }
describe "sign_in" do
before { visit '/' }
it "should add a user to the User model" do
expect { click_link "sign_in" }.to change(User, :count).by(1)
end
it "should route to the appropriate page if email is nil" do
click_link "sign_in"
page.should have_selector('h5', text: 'Edit Email')
end
it "should redirect to the show page upon" do
user.email = 'example#stanford.edu'
user.save!
click_link 'sign_in'
page.should have_selector('div', text: user.name)
end
end
end
Factory
FactoryGirl.define do
factory :user do
provider "facebook"
uid '1234567'
name 'Jonathan'
end
end
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 have a Rails app and I am trying to test it. I use devise to log in. However, I faced a problem that occurs when I want to test:
Users/ender/Projects/ratingw/spec/controllers/widgets_controller_spec.rb:4:in `block in <top (required)>': undefined local variable or method `login_user' for #<Class:0x007fe909bd5070> (NameError)
First, I want to say that I read this devise formal tutorial.
My spec_helper.rb is:
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rspec'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = {
:provider => 'twitter',
:uid => '123545'
# etc.
}
OmniAuth.config.mock_auth[:twitter] = :invalid_credentials
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
config.include RequestMacros, :type => :request
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
end
module ::RSpec::Core
class ExampleGroup
include Capybara::DSL
include Capybara::RSpecMatchers
end
end
and also I have a controller_macros.rb, which is located in support file:
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = Factory(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in user
end
end
end
and finally, my controller_spec file is:
require 'spec_helper'
describe WidgetsController do
login_user
describe "User" do
before(:each) do
#current_user = mock_model(User, :id => 1)
#widget = mock_model(Widget, :user_id => 1)
Widget.stub!(:current_user).and_return(#current_user)
Widget.stub!(:widgets).and_return(#widget)
end
it "should have a current_user" do
subject.current_user.should_not be_nil
redirect_to widgets_path
end
it "should not have a current_user" do
redirect_to widgets_path new_user_session_path
end
end
def mock_widget(stubs={})
#mock_widget ||= mock_model(Widget, stubs).as_null_object
end
describe "Get index" do
it "should get all widgets " do
Widget.stub(:all) { [mock_widget] }
get :index
assigns(:widgets) == #widgets
end
end
describe "Post widget" do
it "creates a new widget" do
Widget.stub(:new).with({'these' => 'params'}) { mock_widget(:save => true) }
post :create, :widget => {'these' => 'params'}
assigns(:widget) == #widgets
response.should redirect_to (edit_widget_path(#mock_widget))
end
it "can not create a new widget" do
Widget.stub(:new).with({'these' => 'params'}) { mock_widget(:save => false) }
post :create, :widget => {'these' => 'params'}
assigns(:widget) == #widgets
redirect_to new_widget_path
end
end
describe "Get widget" do
it "shows exact widget via its uuid" do
Widget.stub(:find).with("10") { mock_widget(:save => true) }
get :show
assigns(:widget) == #widget
end
end
describe "Put widget" do
it "updates the widget attributes" do
Widget.stub(:find_by_uuid).with("6").and_return(mock_widget(:update_attributes => true))
mock_widget.should_receive(:update_attributes).with({'these' => 'params'})
put :update, :uuid => "6", :widget => {'these' => 'params'}
response.should redirect_to (edit_widget_path(#mock_widget))
end
it "can not update the widget attributes" do
Widget.stub(:find_by_uuid).with("6").and_return(mock_widget(:update_attributes => false))
mock_widget.should_receive(:update_attributes).with({'these' => 'params'})
put :update, :uuid => "6", :widget => {'these' => 'params'}
end
end
describe "delete destroy" do
it "destroys the requested widget" do
Widget.stub(:find_by_uuid).with("10").and_return(mock_widget)
mock_widget.should_receive(:destroy)
get :destroy, :uuid => "10"
end
end
end
How can I fix this? What is the problem?
I guess the error stems from:
config.extend ControllerMacros, :type => :controller
You should have:
config.include ControllerMacros, :type => :controller
I am doing functional tests for my controllers with Rspec. I have set my default response format in my router to JSON, so every request without a suffix will return JSON.
Now in rspec, i get an error (406) when i try
get :index
I need to do
get :index, :format => :json
Now because i am primarily supporting JSON with my API, it is very redundant having to specify the JSON format for every request.
Can i somehow set it to default for all my GET requests? (or all requests)
before :each do
request.env["HTTP_ACCEPT"] = 'application/json'
end
Put this in spec/support:
require 'active_support/concern'
module DefaultParams
extend ActiveSupport::Concern
def process_with_default_params(action, parameters, session, flash, method)
process_without_default_params(action, default_params.merge(parameters || {}), session, flash, method)
end
included do
let(:default_params) { {} }
alias_method_chain :process, :default_params
end
end
RSpec.configure do |config|
config.include(DefaultParams, :type => :controller)
end
And then simply override default_params:
describe FooController do
let(:default_params) { {format: :json} }
...
end
The following works for me with rspec 3:
before :each do
request.headers["accept"] = 'application/json'
end
This sets HTTP_ACCEPT.
Here is a solution that
works for request specs,
works with Rails 5, and
does not involve private API of Rails (like process).
Here's the RSpec configuration:
module DefaultFormat
extend ActiveSupport::Concern
included do
let(:default_format) { 'application/json' }
prepend RequestHelpersCustomized
end
module RequestHelpersCustomized
l = lambda do |path, **kwarg|
kwarg[:headers] = {accept: default_format}.merge(kwarg[:headers] || {})
super(path, **kwarg)
end
%w(get post patch put delete).each do |method|
define_method(method, l)
end
end
end
RSpec.configure do |config|
config.include DefaultFormat, type: :request
end
Verified with
describe 'the response format', type: :request do
it 'can be overridden in request' do
get some_path, headers: {accept: 'text/plain'}
expect(response.content_type).to eq('text/plain')
end
context 'with default format set as HTML' do
let(:default_format) { 'text/html' }
it 'is HTML in the context' do
get some_path
expect(response.content_type).to eq('text/html')
end
end
end
FWIW, The RSpec configuration can be placed:
Directly in spec/spec_helper.rb. This is not suggested; the file will be loaded even when testing library methods in lib/.
Directly in spec/rails_helper.rb.
(my favorite) In spec/support/default_format.rb, and be loaded explicitly in spec/rails_helper.rb with
require 'support/default_format'
In spec/support, and be loaded by
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
which loads all the files in spec/support.
This solution is inspired by knoopx's answer. His solution doesn't work for request specs, and alias_method_chain has been deprecated in favor of Module#prepend.
In RSpec 3, you need make JSON tests be request specs in order to have the views render. Here is what I use:
# spec/requests/companies_spec.rb
require 'rails_helper'
RSpec.describe "Companies", :type => :request do
let(:valid_session) { {} }
describe "JSON" do
it "serves multiple companies as JSON" do
FactoryGirl.create_list(:company, 3)
get 'companies', { :format => :json }, valid_session
expect(response.status).to be(200)
expect(JSON.parse(response.body).length).to eq(3)
end
it "serves JSON with correct name field" do
company = FactoryGirl.create(:company, name: "Jane Doe")
get 'companies/' + company.to_param, { :format => :json }, valid_session
expect(response.status).to be(200)
expect(JSON.parse(response.body)['name']).to eq("Jane Doe")
end
end
end
As for setting the format on all tests, I like the approach from this other answer: https://stackoverflow.com/a/14623960/1935918
Perhaps you could add the first answer into spec/spec_helper or spec/rails_helper with this:
config.before(:each) do
request.env["HTTP_ACCEPT"] = 'application/json' if defined? request
end
if in model test (or any not exist request methods context), this code just ignore.
it worked with rspec 3.1.7 and rails 4.1.0
it should be worked with all rails 4 version generally speaking.
Running Rails 5 and Rspec 3.5 I had to set the headers to accomplish this.
post '/users', {'body' => 'params'}, {'ACCEPT' => 'application/json'}
Thi matches what the example in the docs looks like:
require "rails_helper"
RSpec.describe "Widget management", :type => :request do
it "creates a Widget" do
headers = {
"ACCEPT" => "application/json", # This is what Rails 4 accepts
"HTTP_ACCEPT" => "application/json" # This is what Rails 3 accepts
}
post "/widgets", { :widget => {:name => "My Widget"} }, headers
expect(response.content_type).to eq("application/json")
expect(response).to have_http_status(:created)
end
end
Per the Rspec docs, the supported method is through the headers:
require "rails_helper"
RSpec.describe "Widget management", :type => :request do
it "creates a Widget" do
headers = {
"ACCEPT" => "application/json", # This is what Rails 4 and 5 accepts
"HTTP_ACCEPT" => "application/json", # This is what Rails 3 accepts
}
post "/widgets", :params => { :widget => {:name => "My Widget"} }, :headers => headers
expect(response.content_type).to eq("application/json")
expect(response).to have_http_status(:created)
end
end
For those folks who work with request tests the easiest way I found is to override #process method in ActionDispatch::Integration::Session and set default as parameter to :json like this:
module DefaultAsForProcess
def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: :json)
super
end
end
ActionDispatch::Integration::Session.prepend(DefaultAsForProcess)
Not sure if this will work for this specific case. But what I needed in particular was to be able to pass a params hash to the post method. Most solutions seem to be for rspec 3 and up, and mention adding a 3rd parameter like so:
post '/post_path', params: params_hash, :format => 'json'
(or similar, the :format => 'json' bit varies)
But none of those worked. The controller would receive a hash like: {params: => { ... }}, with the unwanted params: key.
What did work (with rails 3 and rspec 2) was:
post '/post_path', params_hash.merge({:format => 'json'})
Also check this related post, where I got the solution from: Using Rspec, how do I test the JSON format of my controller in Rails 3.0.11?
Why don't RSpec's methods, "get", "post", "put", "delete" work in a controller spec in a gem (or outside Rails)?
Based off this question, you could try redefining process() in ActionController::TestCase from https://github.com/rails/rails/blob/32395899d7c97f69b508b7d7f9b7711f28586679/actionpack/lib/action_controller/test_case.rb.
Here is my workaround though.
describe FooController do
let(:defaults) { {format: :json} }
context 'GET index' do
let(:params) { defaults }
before :each do
get :index, params
end
# ...
end
context 'POST create' do
let(:params) { defaults.merge({ name: 'bar' }) }
before :each do
post :create, params
end
# ...
end
end
I'm trying to write an rspec for my Rooms controller to test permissions with CanCan but keep getting the error in the title. I'm following the steps here under Controller Testing: https://github.com/ryanb/cancan/wiki/Testing-Abilities
room_controller_spec.rb
require 'spec_helper'
describe RoomsController do
before(:each) do
#user_1 = Factory.create(:user, :password => 'password')
#room_for_user_1 = Room.create(:user_id => #user_1.id)
#ability = Object.new
#ability.extend(CanCan::Ability)
#controller.stubs(:current_ability).returns(#ability)
end
describe "Room Permissions" do
it "should allow a user to join a room" do
#ability.can :show, #room_for_user_1
get :show, { :uuid => #room_for_user_1.uuid }
response.should render_template("show")
end
end
end
Any advice on how I can get devise + CanCan + RSpec working so I can test the controller? Thanks
That's not RSpec syntax, what you want is:
#controller.stub!(:current_ability).and_return(#ability)
Newer Version 3.8 allow Syntax.
allow( #controller ).to receive( :current_ability ).and_return( #ability )
As per the Relish 3.8 docs.