Testing Rails controller method with minitest - ruby-on-rails

Using minitest, what's the best way to test this build_plan method?
My current attempt is to try to verify that #account.plan is getting set, but I can't quite figure out how to do that. Is this what I should be trying to do, or something else?
accounts_controller.rb
class AccountsController < ApplicationController
before_filter :build_plan, :only => [:new, :create]
def build_plan
redirect_to '/app/signup' and return unless #plan = SubscriptionPlan.find_by_name(params[:plan])
#plan.discount = #discount
#account.plan = #plan
end
end
account_integration_test.rb
require 'minitest_helper'
describe "Account integration" do
before do
#account = Factory.build(:account)
end
def fill_in_info
fill_in 'First name', :with => #account.admin.first
fill_in 'Last name', :with => #account.admin.last
fill_in 'Email', :with => #account.admin.email
end
describe "register" do
it "should set plan" do
visit signup_path(:plan => "small_group")
fill_in_info
click_button 'Create Account'
#account.plan.must_be_kind_of SubscriptionPlan #this doesn't work -- #account.plan is nil
end
end
end

For acceptance tests make sure you have the correct (presumably Capybara) DSL included in 'minitest_helper' e.g.
class MiniTest::Spec
include Capybara::DSL
...
end
It's worth reading a few other StackOverflow posts to get the right helper setup.
This one Has anyone used Minitest::Spec withing a Rails functional test? was helpful to me.
Here's a Gist of my test helper for functional tests (I have yet to add the Capybara module as above) https://gist.github.com/2990759

Related

Testing will paginate gem with Rspec/Capybara

In my application there is an admin part, which is restricted to superadmins (users with a property superadmin: true). I've got a shop list, which I want to get paginated and tested.
When debugging the current code with save_and_open_page I get a blank page. If I log in not as a superadmin, I get redirected to application's root and when trying to debug with save_and_open_page is see the root page.. If I do not log in at all, then I'll get redirected to the sign in page. So the basic functionality should work.
I'm having no clue why it does not work with superadmin and why I do not see the shops list when debugging with save_and_open_page.
This is my spec/controllers/shops_controller_spec.rb (copied basically from here) :
require 'rails_helper'
RSpec.describe Admin::ShopsController, type: :controller do
context "GET methods" do
describe "#index action" do
before(:all) {
amount = Rails.application.config.page_size
amount.times { FactoryGirl.create(:shop) }
}
before(:each) {
login_as(FactoryGirl.create(:user, superadmin: true), :scope => :user)
}
context "with entries == config.page_size" do
it "has no second page" do
get :index
expect(response).not_to have_selector("a", :href => "/shops?page=2", :content => "2")
# visit admin_shops_path
# expect(page).to have_no_xpath("//*[#class='pagination']//a[text()='2']")
end
end
context "with entries > config.page_size" do
before { FactoryGirl.create(:shop) }
it "has a second page with too many entries" do
visit "/admin/shops"
save_and_open_page
expect(page).to have_xpath("//*[#class='pagination']//a[text()='2']")
end
it "correctly redirects to next page" do
visit admin_shops_path
find("//*[#class='pagination']//a[text()='2']").click
expect(page.status_code).to eq(200)
end
end
end
end
end
As you can see, I tried to test in different ways (the "expect block" is taken from this SO-question), but none of them work. Using get :index I receive
Admin::ShopsController GET methods #index action with entries == config.page_size has no second page
Failure/Error: expect(page).not_to have_selector("a", :href => "/shops?page=2", :content => "2")
ArgumentError:
invalid keys :href, :content, should be one of :count, :minimum, :maximum, :between, :text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set
Here is my AdminController.rb if it helps:
class AdminController < ApplicationController
layout 'admin'
before_action :authenticate_user!, :verify_is_superadmin
before_action :set_locale
before_action :get_breadcrumbs
private
def get_breadcrumbs
splitted_url = request.original_fullpath.split("/")
# Remove first object
splitted_url.shift
result = splitted_url.map { |element| element.humanize.capitalize }
session[:breadcrumbs] = result
# debug
end
def set_locale
I18n.locale = params[:locale] || session[:locale] || I18n.default_locale
# session[:locale] = I18n.locale
end
def verify_is_superadmin
(current_user.nil?) ? redirect_to(root_path) : (redirect_to(root_path) unless current_user.superadmin?)
end
end
Update
Using Thomas' answer I ended up putting my code in spec/features and it looks like this right now:
require "rails_helper"
RSpec.feature "Widget management", :type => :feature do
before(:each) {
amount = Rails.application.config.page_size
amount.times { FactoryGirl.create(:shop) }
}
before(:each) {
login_as(FactoryGirl.create(:user, superadmin: true), :scope => :user)
}
scenario "with entries == config.page_size" do
visit admin_shops_path
#save_and_open_page
expect(page).to have_no_xpath("//*[#class='pagination']//a[text()='2']")
end
scenario "with entries > config.page_size" do
FactoryGirl.create(:shop)
visit admin_shops_path
expect(page).to have_xpath("//*[#class='pagination']//a[text()='2']")
end
scenario "with entries > config.page_size it correctly redirects to next page" do
FactoryGirl.create(:shop)
visit admin_shops_path
find("//*[#class='pagination']//a[text()='2']").click
expect(page.status_code).to eq(200)
end
end
Everything works!
You've got a number of issues here.
Firstly the other SO question you linked to isn't using Capybara so copying its examples for matchers is wrong.
Secondly you are writing controller tests, not view tests or feature tests. controller tests don't render the page by default, so to test elements on the page you want to be writing either view tests or feature tests. Capybara is designed for feature tests and isn't designed for controller tests. This is why the default capybara/rspec configuration file only includes the Capybara DSL into tests of type 'feature'. It also includes the Capybara RSpec matchers into view tests since they are useful with the rendered strings provided there.
Thirdly, you are mixing usage of get/response, and visit/page in the same file which just confuses things.
If you rewrite these as feature tests, then to check you don't have a link with a specific href in capybara you would do
expect(page).not_to have_link(href: '...')
If you want to make sure that a link doesn't exist with specific text and a specific href
expect(page).not_to have_link('link text', href: '...')
Note: that checks there is not a link with both the given text and the given href, there could still be links with the text or the href

Testing for a Successful Redirect with Capybara + RSpec

Righto, I just want to test that after a user logs in, he is redirected a specific page.
Here's the spec:
require 'rails_helper'
describe "the signin process", :type => :feature do
it "signs me in and redirects" do
visit ("/")
within("#login_box") do
fill_in 'user_email', :with => 'user#gmail.com'
fill_in 'user_password', :with => 'password'
end
click_button 'Sign in'
#binding.pry
expect(page.current_path).to have_content 'erb'
end
end
Here's the snippet of code I added to ApplicationController for a devise based redirect:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def after_sign_in_path_for(resource)
session["user_return_to"] || home_preparation_path
end
end
It works fine manually testing in the browser (I am successfully redirected to the home_preparation_path).
Oh, and the view for that path is just the title:
#preparation.html.erb
preparation.html.erb
The failed test msg I get returns:
Failure/Error: expect(page.current_path).to have_content 'erb'
expected to find text "erb" in "/users/sign_in"
That path is most definitely one defined by Devise. It looks like Capybara isn't automatically going through the motions of redirects, in that it just stops at the Devise controller. What am I missing here to write a proper, passing test?

RSpec stub not working for integration test

Inside a controllers test, I want to test that when logged in, the controller renders the request fine, else if not logged in, it redirects to the login_path.
The first test passes fine as expected, no user is logged in, so the request is redirected to the login_path. However I've tried a myriad of stub/stub_chain's but still can't get the test to fake a user being logged in and render the page okay.
I would appreciate some direction on getting this to work as expected.
The following classes and tests are the bare bones to keep the question terse.
ApplicationController
class ApplicationController < ActionController::Base
include SessionsHelper
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
SessionsHelper
module SessionsHelper
def logged_in?
redirect_to login_path, :notice => "Please log in before continuing..." unless current_user
end
end
AppsController
class AppsController < ApplicationController
before_filter :logged_in?
def index
#title = "apps"
end
end
apps_controller_spec.rb
require 'spec_helper'
describe AppsController do
before do
#user = FactoryGirl.create(:user)
end
describe "Visit apps_path" do
it "should redirect to login path if not logged in" do
visit apps_path
current_path.should eq(login_path)
end
it "should get okay if logged in" do
#stubs here, I've tried many variations but can't get any to work
#stubbing the controller/ApplicationController/helper
ApplicationController.stub(:current_user).and_return(#user)
visit apps_path
current_path.should eq(apps_path)
end
end
end
This is not working because you are stubbing the method current_user on the ApplicationController class, and not an instance of that class.
I would suggest stubbing it (correctly) on an instance of that class, but your test appears to be an integration test rather than a controller test.
What I would do instead then is as Art Shayderov mentioned is to emulate the sign-in action for a user before attempting to visit a place that requires an authenticated user.
visit sign_in_path
fill_in "Username", :with => "some_guy"
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
In my applications, I've moved this into a helper method for my tests. This is placed into a file at spec/support/authentication_helpers.rb and looks like this:
module AuthenticationHelpers
def sign_in_as!(user)
visit sign_in_path
fill_in "Username", :with => user.username
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
end
end
RSpec.configure do |c|
c.include AuthenticationHelpers, :type => :request
end
Then in my request specs, I simply call the method to sign in as that particular user:
sign_in_as(user)
Now if you want to sign in using a standard controller test, Devise already has helpers for this. I generally include these in the same file (spec/support/authentication_helpers.rb):
RSpec.configure do |c|
c.include Devise::TestHelpers, :type => :controller
end
Then you can sign in using the helpers like this:
before do
sign_in(:user, user)
end
it "performs an action" do
get :index
end
I would look at http://ruby.railstutorial.org/chapters/sign-in-sign-out#sec:a_working_sign_in_method.
The author describes how to write a sign_in method and use it in your rspec tests.
It doesn't look like controller test. It looks more like rspec-rails request spec which simulates browser. So stabbing controller won't work, you have to either simulate sign in (something like this)
visit sign_in
fill_in 'username', :with => 'username'
...
or manually add user_id to session.
If on the other hand you want to test controller in isolation your test should look like that:
get 'index'
response.should be_success

Testing with Rspec, NiftyAuthentication

I'm using Ryan Bates's nifty:authentication, and starting testing with Rspec. Fought with this for weeks, and still don't understand what's happening.
My controller simply calls
before_filter :login_required, :except => [:login]
Which is defined in lib/controller_authentication
def self.included(controller)
controller.send :helper_method, :current_account, :logged_in?, :redirect_to_target_or_default
end
def current_account
#current_account ||= Account.find(session[:account_id]) if session[:account_id]
end
def logged_in?
current_account
end
def login_required
unless logged_in?
store_target_location
redirect_to login_url, :alert => "You must first answer me these riddles three log in or sign up before accessing this page."
end
end
def redirect_to_target_or_default(default, *args)
redirect_to(session[:return_to] || default, *args)
session[:return_to] = nil
end
private
def store_target_location
session[:return_to] = request.url
end
end
The app works as intended, but the testing fails every time. No matter what I try, I get the redirect_to login_url, :alert => "You must first ...log in" page.
Things I've tried:
controller.stub!( :login_required )
ControllerAuthentication.stub!(:current_account).and_return(Account.where(:username => 'ej0c').first)
#ControllerAuthentication.stub!(:logged_in?).and_return(Account.where(:username => 'ej0c').first)
ControllerAuthentication.stub!(:login_required).and_return(true)
MyDigisController.stub!( :login_required ).and_return(true)
Which I think means I'm missing the whole theory of the thing. How can I make my login work?
I tried as Punit suggests below:
[pre]
require 'spec_helper'
describe "View event details" do
it "Should show a table of events" do
#account = Account.where(:username => 'ej0c').first
puts #account.inspect
controller.stub!(:current_account).and_return(#account)
controller.stub!(:logged_in?).and_return(true)
session[:account_id] = #account.id
visit '/my_digis/66/custom_events'
page.should have_content('Events')
end
end
#account.inspect displayed nicely, but I also got
An expectation of :current_account was set on nil. Called from C:/Users/Ed/webapps/whendidji3/spec/con
.rb:8:in `block (2 levels) in <top (required)>'. Use allow_message_expectations_on_nil to disable warn
An expectation of :logged_in? was set on nil. Called from C:/Users/Ed/webapps/whendidji3/spec/controll
:in `block (2 levels) in <top (required)>'. Use allow_message_expectations_on_nil to disable warnings.
Thanks for any detailed explanations, as I've searched high an low to understand what's goin on.
You are using a vanilla spec rather than a controller spec, which means that the variable 'controller' is not being set.
To use a controller spec, you need to pass the controller class name to your describe block rather than a string.
describe MyController do
see http://rspec.info/rails/writing/controllers.html
Once you have that going, you should be able to use your original thought of stubbing login_required
You need to stub the "current_account" method. You could do it like this -
#account = create_account #function to create an account for your specs
controller.stub!(:current_account).and_return(#account)
controller.stub!(:logged_in?).and_return(true)
You should probably create a method out of the above lines to stub authentication wherever required.
OK, the answer was that I needed a request spec.
I'd begun with a request spec, but it wasn't in the requests folder, and the questions I asked got it morphed into a half-request/half-controller spec, none of which works.
The strange thing about rspec is that it will complain if a capybara method is badly formed,...but it won't mention that the thing just plain doesn't work where you put it!
My working spec, located in specs/requests/my_digis_spec.rb is
require 'spec_helper'
describe "MyDigis" do
before :each do
#account = Account.where(:username => 'me').first
visit login_path
fill_in 'Username or Email Address', :with => #account.email
fill_in 'Password', :with => 'password'
click_button('Log in')
end
it "Shows list of digis" do
visit my_digis_path
page.should have_content('Your Custom Event groups')
end
it "Lets you modify didji list" do
visit my_digis_path
click_button('Modify events')
page.should have_content('Events for my_digi')
end
end
As easy as advertised, just took me 5 weeks to get the login part. Thanks.

Testing Sessions in Rails 3 with Rspec & Capybara

I'm trying to write integration tests with rspec, factory_girl & capybara. I also have cucumber installed, but I'm not using it (to my knowledge).
I basically want to prepopulate the db with my user, then go to my home page and try to log in. It should redirect to user_path(#user).
However, sessions don't seem to be persisted in my /rspec/requests/ integration tests.
My spec: /rspec/requests/users_spec.rb
require 'spec_helper'
describe "User flow" do
before(:each) do
#user = Factory(:user)
end
it "should login user" do
visit("/index")
fill_in :email, :with => #user.email
fill_in :password, :with => #user.password
click_button "Login"
assert current_path == user_path(#user)
end
end
Returns:
Failures:
1) User flow should login user
Failure/Error: assert current_path == user_path(#user)
<false> is not true.
# (eval):2:in `send'
# (eval):2:in `assert'
# ./spec/requests/users_spec.rb:16
Instead, it redirects to my please_login_path - which should happen if the login fails for any reason (or if session[:user_id] is not set).
If I try to put session.inspect, it fails as a nil object.
If I try to do this in the controller tests (/rspec/controllers/sessions_spec.rb), I can access the session with no problem, and I can call session[:user_id]
If you are using Devise, you'll need to include Warden::Test::Helpers (right after the require of spec_helper is a good place) as outlined in the warden wiki.
The call to session is returning nil because capybara doesn't provide access to it when running as an integration test.
I have the same problems and although filling out a form might be an option for some, I had to roll my own authentication ruby because I was using a third party auth system (Janrain to be exact).... in my tests I ended up using something like this:
Here is what I have in my spec/support/test_helpers_and_stuff.rb
module AuthTestHelper
class SessionBackdoorController < ::ApplicationController
def create
sign_in User.find(params[:user_id])
head :ok
end
end
begin
_routes = Rails.application.routes
_routes.disable_clear_and_finalize = true
_routes.clear!
Rails.application.routes_reloader.paths.each{ |path| load(path) }
_routes.draw do
# here you can add any route you want
match "/test_login_backdoor", to: "session_backdoor#create"
end
ActiveSupport.on_load(:action_controller) { _routes.finalize! }
ensure
_routes.disable_clear_and_finalize = false
end
def request_signin_as(user)
visit "/test_login_backdoor?user_id=#{user.id}"
end
def signin_as(user)
session[:session_user] = user.id
end
end
Then in my request spec, with capybara and selenium, I did the following:
describe "Giveaway Promotion" do
context "Story: A fan participates in a giveaway", js: :selenium do
context "as a signed in user" do
before :each do
#user = Factory(:user)
request_signin_as #user
end
it "should be able to participate as an already signed in user" do
visit giveaway_path
....
end
end
end
end
BTW, I came up with solutions after trying the proposed solutions to this post and this post and neither of them worked for me. (but they certainly inspired my solution)
Good luck!
You've probably moved on from this, but I was just struggling with the same question. Turns out it was a matter of syntax. I was using symbols for :email and :password and I should've been using strings instead ("email" and "password").
In other words, try changing this:
fill_in :email, :with => #user.email
fill_in :password, :with => #user.password
to this:
fill_in "email", :with => #user.email
fill_in "password", :with => #user.password

Resources