How can I get Minitest to resolve helper in a controller test? - ruby-on-rails

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.

Related

"NoMethodError: undefined method `env' for nil:NilClass" for Helper testing in rails

I tried to google but I find nowhere about this error for ActionView
I am using Minitest (no Rspec)
custom_user_groups_helper_test.rb
class CustomUserGroupsHelperTest < ActionView::TestCase
test "should work custom user" do
response_params = gotta_get
end
end
custom_user_groups_helper.rb
module CustomUserGroupsHelper
def self.gotta_get
return true
end
This test_helper.rb works fine with the controller test, but when I use it for helper test
it throws me this error
Error:
CustomUserGroupsHelperTest#test_should_work_custom_user:
NoMethodError: undefined method `env' for nil:NilClass
I tried another solution too
"undefined method `env' for nil:NilClass" in 'setup_controller_for_warden' error when testing Devise using Rspec
But this seems to be for the controller, none of the solutions is for the helper testing.
What to include for ActionView::TestCase from Devise
I tried to include these
include Devise::TestHelpers
include Devise::Test::IntegrationHelpers
include Devise::Test::ControllerHelpers
As one of the solutions on StackOverflow says
The Devise::TestHelpers should only be included for controller tests, so you need to move it to the ActionController::TestCase by adding this to your test helpers:
Then what to include for Helper test which inherits ActionView::TestCase
How to test helpers in the Minitest framework.
But i suppose include Devise::Test::IntegrationHelpers and include Devise::Test::ControllerHelpers are for controllers for different rails versions.
error
Updated error stack trace
stack trace for test run
CustomUserGroupsHelperTest: test_should_work_custom_user
--------------------------------------------------------
(0.1ms) ROLLBACK
E (0.1ms) BEGIN
You seem to have fundamentially missunderstood both what a helper is in Rails and how you should test them. Which is really understandable as its a really vague term and means different things in different contexts.
Helpers (as in /app/helpers) in a Rails app are just modules that are mixed into the view context and and provide simple helper methods for stuff like generating html or getting the user from the session in the case of Devise.
A test like this:
require 'test_helper'
class CustomUserGroupsHelperTest < ActionView::TestCase
test "should work custom user" do
post :save_custom_groups
assert_equal "save_custom_groups", #controller.action_name
end
end
Is just wrong in every way possible. A helper does not respond to HTTP methods. This is more like the strangest attempt at a controller test ever created.
To test helpers you should just test it like a plain old method. Give it input and test the output:
module ListHelper
def ul(array)
content_tag :ul do
array.each do |obj|
concat content_tag :li, obj
end
end
end
end
class ListHelperTest < ActionView::TestCase
test "should return the user's full name" do
assert_dom_equal %{<ul><li>foo</li><li>bar</li></ul>}, ul(["foo", "bar"])
end
end
If you are testing a helper that relies on a method from Devise like current_user, user_signed_in? etc you should just stub these methods. A helper test is not full stack and thus has no real session.
You can stub other helper methods in ActionView::TestCase by stubbing them on self. If you are using mocha for example you would do:
class UsersHelperTest < ActionView::TestCase
def setup
#user = users(:david)
self.stubs(:current_user).returns(#user)
end
test "should link to the current user" do
assert_dom_equal %{#user.name}, link_to_current_user
end
end
Devise::Test::IntegrationHelpers and Devise::Test::ControllerHelpers are for integration and controller tests where you are actually driving most of the framework and are completely irrelevant for helper tests. These are examples of test helpers which is a behavior that you mix into your tests.
You can of course test helpers indirectly with integration tests that render the view where the helper is used but since helpers are relatively simple it it can be a good idea to test them directly.

Controller specs when stubbing devise authentication are never invoking controller action

So I'm running into a very weird issue when attempting to write some controller specs for a rails application I am working on. Basically at a high level it seems like the spec is never actually invoking the controller action. A couple of things that I've done and found..
After doing some looking around I attempted to see if it was possibly a routing issue and to test this I used controller.index and sure enough the action is invoked and the test passed. That being said this obviously leads to issues when I'm attempting to call actions that require params so I'd like to get #get|#post etc. working.
When I run expect(response).to be_successfuland write fail 'some message' in the controller action the test still passes, which further proves nothing is occurring surrounding the controller action.
I went about preventing devise from authenticating the user by removing before_filter :authenticate_user! from the application_controller and then for whatever reason the spec runs and hits the action correctly and the spec passes. Now, this is occurring even when using #get which works against the theory of this being a routing issue.
Because of the last point I think the problem lies somewhere in how the devise auth is being handled so to touch on that the only thing in this stack that isn't straight out of the box devise-wise is a controller helper for authentication that I use before hitting protected views which is pulled directly from the devise wiki on specs. It can be see here:
def sign_in(user = double('user'))
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
Now even though I think the issue is in devise it's probably a good idea to give examples of everything else. So let me give you a lay of the land (of course some stuff is scrubbed out and simplified):
routes.rb:
namespace :api, path: '', constraints: { subdomain: 'api' }, defaults: { format: :json } do
namespace :v1 do
..
resources :accounts
end
end
alerts_controller.rb:
class AlertsController < ApplicationController
respond_to :html
def index
#alerts = Alert.all
end
end
alerts_controller_spec.rb:
require 'rails_helper'
RSpec.describe AlertsController, type: :controller do
let(:user){ create :user, :admin }
let(:alert){ create :alert }
before do
sign_in user
end
describe 'GET index' do
it 'responds successfully' do
get :index
expect(assigns(:alerts)).to eq [alert]
end
end
end
rake routes => GET /v1/alerts(.:format) api/v1/alerts#index {:subdomain=>"api"}
Any help would be highly appreciated as I'm seriously stumped.
Don't write your own helper for that. Devise comes with a helper just for this
Include it for your specs like this
#spec/rails_helper.rb
...
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
end
And then use it. In your case all you need to do is change this
before do
sign_in user
end
to this
before do
sign_in :user, user
end
I figured out the issue and honestly it was pretty stupid and my own fault :P. In my application controller there is a use_user_timezone around filter. The around filter was only added to callbacks if there was a current_user. Earlier on, for some reason, I prevented the code in the around filter from running unless Rails.env.test? == true. Obviously I didn't really have any reason for writing this code.. The problem was that I was not preventing the around_filter from running if it was the test environment, but that I was preventing the code in the filter method from running if it was the test environment. Because of this I was not yielding to any code to execute for the around filter, which was of course causing the issue with the controller action never executing.
The reason that devise was seeming to play an issue in the mix was because the around filter executing was dependent on a current_user being present. Due to this the controller flow was continuing on with no problem when no auth was occurring since there was no current_user.
The wrap this up.. As always it's best to be attentive when making changes like this to a codebase as little bugs like this can cause major headaches later on down the road :).

Testing a Rails controller action with RSpec’s post method

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.

Rails 4 engine controller redirect fails unit testing

I am building a Rails 4 engine that provides some controllers and models that will then be used by several of our apps. I am in the process or writing the unit tests, and I am having problems with the actions in the controllers that make a redirect_to.
In the controller that I am testing, I have the following actions:
def index
end
def new
#block = GlobalIpBlock.new
end
def create
#block = GlobalIpBlock.new(create_params)
if #block.save
flash[:success] = "The IP has been successfully blocked."
redirect_to action: 'index'
else
render 'new'
end
end
and in the controller test I have these two tests:
test "should get new block" do
get :new, use_route: :watchdog
assert_response :ok
assert_not_nil assigns(:block)
end
test "should create global ip block" do
assert_difference('GlobalIpBlock.count') do
post :create, block: {some_param: 'some value'}, use_route: :watchdog
end
assert_redirected_to :index
end
The first test passes but the second throws an error:
ActionController::UrlGenerationError: No route matches {:action=>"index"}
I have no routes created in the engine and in the routes of the dummy test app it only mounts the engine. The reason for that is that I want the hosting apps to provide their own routes to the controllers/actions of the engine.
Still, this doesn't seems to be the issue here since the test for the action new passes. Furthermore, I have tried creating the routes in the engine by doing:
resources :global_ip_blocks, except: [:edit, :update]
but that didn't help, neither did doing that in the routes of the dummy test app.
I am guessing the redirect_to is not finding the route in the same way that removing use_route: :watchdog from the get/post in the tests fail but how can I fix that? Is there something like a global way of telling unit tests to use_route: :watchdog?
You should be able to fix this using:
class MyControllerTest < ActionController::TestCase
def setup
#routes = MyEngine::Engine.routes
end
end
Also, look out for the deprecation notice in the logs due to the use of use_route which is like:
DEPRECATION WARNING: Passing the use_route option in functional tests are deprecated. Support for this option in the process method (and the related get, head, post, patch, put and delete helpers) will be removed in the next version without replacement. Functional tests are essentially unit tests for controllers and they should not require knowledge to how the application's routes are configured. Instead, you should explicitly pass the appropiate params to the process method. Previously the engines guide also contained an incorrect example that recommended using this option to test an engine's controllers within the dummy application. That recommendation was incorrect and has since been corrected. Instead, you should override the #routes variable in the test case with Foo::Engine.routes. See the updated engines guide for details.

Rails Devise testhelpers: sign_in is working with get but not post

I'm working on functionality testing on a Rails website that uses Devise, and I've run into a strange problem. I needed to figure out how to simulate a login, and after a little searching, I found this question that told me about Devise::TestHelpers and the sign_in function. Oddly enough, though, it's working in some but not all of my tests. Here's a couple of my tests:
include Devise::TestHelpers
setup do
#game = games(:game_one)
#user = users(:one)
sign_in(#user)
end
# This test passes
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:games)
end
# This test fails; the post redirects to /users/sign_in
# as if accessed without logging in
test "should create game" do
assert_difference('Game.count') do
post :create, game: { name: #game.name }
end
assert_redirected_to game_path(assigns(:game))
end
And the controller itself looks like this:
class GamesController < ApplicationController
before_filter :authenticate_user!
# Some mostly-autogenerated CRUD
end
From what I can tell, the main difference between the tests that are working and the tests that think I haven't logged in is that the former are using get and the latter post. Is that something that makes sense? And how can I fix it?
Edit: Is this something to do with the "scope" that the Devise readme mentions? It doesn't really explain what that means, but it seems like it may be relevant.
Aha! Thanks to farleyknight's comment, I discovered it was complaining about CSRF token authenticity. From there, this question had a working solution. I added this to my ApplicationController:
skip_before_filter :verify_authenticity_token if Rails.env.test?
There's probably a way to actually send valid CSRF tokens as part of the test, which would probably be a better solution, but this one's working for me.

Resources