I have a very simple controller:
class ThingsController < ApplicationController
def create
Thing.do_stuff
end
end
…And a very simple spec:
require "rails_helper"
describe ThingsController do
describe "POST #create" do
it "does stuff with things" do
expect(Thing).to receive(:do_stuff)
controller.create # This works
post :create # This does not work
end
end
end
I am not running the direct invocation and the post request at the same time. Invoking the action on the controller directly passes the assertion, but invoking the action through the post method does not. It appears do_stuff is never called on Thing. Why might that be?
I discovered what my issue was.
Invoking the controller directly keeps the spec isolated and ignores things like a before_action in the ApplicationController.
When we start using the post method, it’s really an integrated test and hits things like authentication. I couldn’t hit my controller method because my test user wasn’t signed in.
Related
I have a failing controller test that is just testing that an index route returns a 200 response.
The controller in question has before_action :is_authenticated?, where is_authenticated? is defined in a helper auth_helper.rb included in ActionController (since my intention is to use it across multiple controllers).
When I interact with my app, the is_authenticated? method is executed normally. When I run my test suite, I get the following error message:
# Running:
....E
Error:
EventsControllerTest#test_should_fail_creation_with_invalid_data:
NoMethodError: undefined method `is_authenticated?' for #<Api::V1::EventsController:0x00007f943966c2c0>
test/controllers/events_controller_test.rb:19:in `block in <class:EventsControllerTest>'
rails test test/controllers/events_controller_test.rb:18
Here is my test:
require 'test_helper'
class EventsControllerTest < ActionDispatch::IntegrationTest
setup do
#event = Event.first
register
end
test "should get index" do
get "/api/v1/events"
assert_response :success
events = JSON.parse(response.body)
assert_equal(events.length, 2)
end
end
Here is the code in auth_helper.rb:
module AuthHelper
def is_authenticated?
if is_user_signed_in?
return true
else
render json: { error: "You must be logged in" }, status: :unauthorized
end
end
end
How can I get my test to properly simulate the request, using the helper, just as a live request would?
I've tried requiring the helper in the test and defining it in the setup code instead, but that doesn't seem to help.
I'm new to Rails so any feedback would be greatly appreciated. Thanks!
I ended up solving the problem I was having by putting the code inside of a Concern instead of a helper module.
By including the code in a concern, and then adding the concern to ApplicationController, the is_authenticated? function is picked up by my code, and is resolved during the test suite execution.
It's still not clear to me what the difference is between how regular modules and rails concerns get resolved, but adding a concern has achieved the desired behaviour in this case.
I have a controller file:
some_controller.rb:
class SomeController < ActionController::Base
def get_params
# Do something with params
end
end
And a helper file:
module SomeHelper
def modify_params
get_params
end
end
And rspec File for helper:
require 'spec_helper'
describe SomeHelper do
describe 'check_if_param_has_key' do
it "checks if param has key" do
modify_params.should eql(true)
end
end
Here, I am calling controller's method inside helper method. Now I am writing test cases for helper method modify_param.
But it throws error : NoMethodError: get_params
Is there a way to access controller's method inside helper spec?
Is this the right way to do it? Or Am I missing something in this?
Edited:
The controller is ApplicationController, and method inside it return string containing which controller/method was called on page load, by looking at params[:controller], params[:action]
As the author of RSpec has pointed out, helper tests are conceptually unrelated to controllers. So even if there was a way, you'd likely not want to bring the controller into the picture. You can easily stub out the method call in your spec:
describe SomeHelper do
describe "#foo" do
before do
allow(helper).to receive(:bar).and_return("hello")
end
it { expect(helper.foo).to eql("hello") }
end
end
Even if you had a controller, you'd likely have to stub the method call there, to cover the edge case.
Note that this stubbing a method not defined in the helper will fail if you're using verifying doubles.
My problem has been solved by making get request to the page:
describe 'check if the home page by checking params', :type => :request do
it 'return true if page is Home page' do
get "/homepage"
helper.modify_params.should eql(true)
end
end
In above code, after calling get request to homepage, helper method will have access to all the params and controller action it is calling. All my test cases have been passed.
lib/modules/api.rb:
module Api
require 'net/http'
require 'json'
def send_get_request(url, body)
# some logic
end
end
The controller:
class DashboardController < ApplicationController
include Api
def index
response = send_get_request(_some_, _params_)[:json]
#something = response['something']
end
end
How do I stub the send_get_request method? I tried a feature test:
require 'rails_helper'
describe 'visiting users page'
it 'shows users page' do
visit '/'
allow_any_instance_of(Api).to receive(:send_get_request).with(any_args).and_return({json: {'total_paying' => '3'}})
within('#side-menu') do
click_link 'Users'
end
expect(page).to have_selector('.page-header', text: 'Users')
end
end
but apparently it doesn't work (the test fails because real send_get_request method gets called with params that are incorrect (which they are in tests).
When I tried
expect_any_instance_of(Api).to receive(:send_get_request).once
That expectation passed.
Well, it's the controller that will receive the message. The test, as a client, does not care about how the method is defined, "normally" or mixed-in.
If it were a controller spec, you'd be able to do this:
allow(controller).to receive(:send_get_request).with(...)
As for the feature specs, don't stub anything in there. It's supposed to be the "real" interaction. In feature specs you use capybara (or something) to fill the forms and then check the database (to see if a user got created, etc.)
To shield your feature specs from external api, you can use VCR gem. It basically runs your external query one time, writes the response into a file and then, on subsequent runs, just plays it back, without contacting external api again.
I have a controller, AdminController, which sets the various authorisation levels for the rest of the CMS. Because there are no controller actions, just methods, I began to research ways to test these against controllers.
The conclusion I came to was that they needed to be tested independently of the controllers they are used in (I want to to steer clear of integration testing if possible, like capybara etc).
I found some articles like this one to help me along.
So far I have written this spec which is failing with the errors below. I am not sure about it to be honest and wanted to here what SO community had to say on what I am trying to achieve.
describe AdminController do
controller do
before_filter :authorize_fixture_uploader!
def index
render text: 'Hello World'
end
end
let(:admin){FactoryGirl.create(:admin)}
describe "authentication" do
before do
sign_in admin
allow(controller).to receive(:current_admin).and_return(admin)
end
describe "authorize_fixture_uploader! helper" do
context "signed in" do
before do
allow(:admin).to receive(:authorize_fixture_uploader!).and_return(false)
get :index
end
it "redirects do admin_home_path" do
expect(response).to redirect_to admin_home_path
end
end
end
end
end
and here is the controller
class AdminController < ApplicationController
before_filter :authenticate_admin!
def authorize_fixture_uploader!
unless current_admin.fixture_uploader?
return redirect_to(admin_home_path)
end
end
end
This test is giving me the error
1) AdminController authentication authorize_fixture_uploader! helper signed in redirects do admin_home_path
Failure/Error: allow(:admin).to receive(:authorize_fixture_uploader?).and_return(false)
TypeError:
can't define singleton
I am worried its because my whole approach to this is wrong. Help would most certainly be appreciated.
Updated thanks to #blelump's answer.
I had a type which was causing the first issue. But Now I am getting error
undefined method `authorize_fixture_uploader?' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1::Nested_1::Nested_1:0x007f9357857108>
The logic behind this i throwing me a bit. How am I to test these methods independent of the controllers they are used?
You have a typo:
allow(:admin).to receive(:authorize_fixture_uploader!).and_return(false)
Now you're trying to add authorize_fixture_uploader! to Symbol. Just start with controller variable:
allow(controller).to receive(:authorize_fixture_uploader!).and_return(false)
Aside from the poor attention detail highlight by blelump above, the real flaw in my approach was the lack of routes. I found a very useful article from pivotal labs http://pivotallabs.com/adding-routes-for-tests-specs-with-rails-3/ which saved the day.
Read the article, but it essentially boils down to this.
require 'spec_helper'
class InheritsFromAdminController < AdminController
def show
render :text => "foo"
end
end
describe InheritsFromAdminController do
before do
Rails.application.routes.draw do
# add the route that you need in order to test
match '/foo' => "inherits_from_admin#show"
# re-drawing routes means that you lose any routes you defined in routes.rb
# so you have to add those back here if your controller references them
match '/login' => "sessions/new", :as => login
end
end
after do
# be sure to reload routes after the tests run, otherwise all your
# other controller specs will fail
Rails.application.reload_routes!
end
it "requires logged-in users" do
get :show
response.should redirect_to("/login")
end
end
I have a before_filter on my ApplicationController class and I want to write a test for it? Where should I write this test into? I do not want to go into every subclass controller test file and repeat the test about this filter.
Hence, what is the recommended way to test ApplicationController before_filters?
Note that I am using Rails 3.2.1 with minitest.
My case is slightly different than yours, but I needed to do something similar to test authentication across the site (with Devise). Here's how I did it:
# application_controller.rb
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
end
# application_controller_test.rb
require 'test_helper'
class TestableController < ApplicationController
def show
render :text => 'rendered content here', :status => 200
end
end
class ApplicationControllerTest < ActionController::TestCase
tests TestableController
context "anonymous user" do
setup do
get :show
end
should redirect_to '/users/sign_in'
end
end
If there's specific controllers that need to skip the before filter I'll have a test to make sure they skip it in the specific controller's tests. This isn't quite your situation as I'm interested in the effect of the method, not just knowing it was invoked, but I thought I'd share in case you found it useful.
Improving on #bmaddy answser, you do need to setup routing for the specs to run.
Here is a rails 5 working example:
require 'test_helper'
class BaseController < ApplicationController
def index
render nothing: true
end
end
class BaseControllerTest < ActionDispatch::IntegrationTest
test 'redirects if user is not logedin' do
Rails.application.routes.draw do
get 'base' => 'base#index'
end
get '/base'
assert_equal 302, status
assert_redirected_to 'http://somewhere.com'
Rails.application.routes_reloader.reload!
end
test 'returns success if user is loggedin' do
Rails.application.routes.draw do
get 'base' => 'base#index'
end
mock_auth!
get '/base'
assert_equal 200, status
Rails.application.routes_reloader.reload!
end
end
I now believe that I have to have all my controllers tests test about the before_filter existence and that this filter works as expected. This is because, I cannot know whether a controller uses a skip_before_filter when it shouldn't.
Hence, I decided to use mock (#controller.expects(:before_filter_method)) to make sure that the filter is called. So, for example, in a index action I write in my test:
test "get index calls the before filter method" do
#controller.expects(:before_filter_method)
# fire
get :index
end
This will make sure that my controller calls before_filter_method on the particular action. I have to do this on all my actions tests.
If anyone else has a better solution, let me know.
Usually when I want something like this I just test the expected behaviour without taking into account that this particular behaviour may be implemented in a filter and not in a method per se. So for the following simple scenario :
class Controller < ApplicationController
before_filter :load_resource, :only => [:show, :edit]
def show
end
def edit
end
def index
end
#########
protected
#########
def load_resource
#resource = Model.find(params[:id])
end
end
I would simple test that #show and #edit assign the #resource thing. This works for simple scenarios pretty much ok. If the filter is applied to a lot of actions/controllers then you can extract the testing code and reuse it amongst the tests.