In my ApplicationController I have a method defined as a helper method:
helper_method :some_method_here
How do I test ApplicationController in RSpec at all?
How do I include/call this helper method when testing my views/helpers?
I'm using Rails3 with RSpec2
You can use an anonymous controller to test your ApplicationController, as describe in the RSpec documentation. There's also a section on testing helpers.
You can invoke your helper methods on subject or #controller in the specification.
I have been looking for a solution to this problem and anonymous controller was not what I was looking for. Let's say you have a controller living at app/controllers/application_controller.rb with a simple method which is not bound to a REST path:
class ApplicationController < ActionController:Base
def your_helper_method
return 'a_helpful_string'
end
end
Then you can write your test in spec/controllers/application_controller_spec.rb as follows:
require 'spec_helper'
describe ApplicationController do
describe "#your_helper_method" do
it "returns a helpful string" do
expect(subject.your_helper_method).to eq("a_helpful_string")
end
end
end
While #controller and subject can be used interchangeable here, I would go for subject as its the RSpec idiomatic way for now.
Related
I have something like this controller:
class ApiApplicationController < ActionController::API
before_action :record_information_from_headers
private
def record_information_from_headers
InformationSet.create(info: request.headers['INFO'])
end
end
All other controllers are inherited from ApiApplicationController
I want to test that my callback works before each method in child controllers. And try to use Anonymous controller
require 'rails_helper'
RSpec.describe ApiApplicationController do
controller(ApiApplicationController) do
def index; end
end
let('INFO') { "some information" }
it 'some' do
get :index
expect(InformationSet.last.info).to eq('some information')
end
end
But, first of all, i have error:
"NoMethodError:
undefined method `controller' for RSpec::ExampleGroups::ApiApplicationController:Class"
And secondly, how do I pass the information to the header ?
I've already read How to test ApplicationController method defined also as a helper method? and Rspec controller test for callback after_save
I would be grateful for any help)
You don't have the type: describe ...Controller, type: :controller do
The Setting request headers section of the docs shows how to set request headers for controller specs.
I'm trying to write specs for a Rails helper. This helper calls a method
defined in ApplicationController and exposed through helper_method:
app/helpers/monkeys_helper.rb:
module MonkeysHelper
def current_monkey_banana_count
# current_monkey is defined in ApplicationController
current_monkey.present? ? current_monkey.banana_count : 0
end
end
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
helper_method :current_monkey
protected
def current_monkey
#current_monkey ||= Monkey.find(session[:monkey_id])
end
end
If I call current_monkey_banana_count from a view and access it through the browser, it works fine. But if I call it from a spec like this:
spec/helpers/monkeys_helper_spec.rb:
RSpec.describe MonkeysHelper, type: :helper do
describe "#current_monkey_banana_count" do
it "returns 0 if there is no monkey" do
expect(helper.current_monkey_banana_count).to eq 0
end
end
end
Then I get this error when I run the spec:
NameError:
undefined local variable or method `current_monkey' for #<#<Class:0x007fe1ed38d700>:0x007fe1e9c72d88>
Rspec documentation says:
To access the helper methods you're specifying, simply call them
directly on the helper object. NOTE: helper methods defined in
controllers are not included.
Any idea how to either mock current_monkey or make it visible from inside current_monkey_banana_count?
Thanks!
I found a (nasty) way to do it, but it works:
spec/helpers/monkeys_helper_spec.rb:
require 'rails_helper'
RSpec.describe CartsHelper, type: :helper do
before do
def helper.current_monkey; end
end
describe "#current_monkey_banana_count" do
it "returns 0 if there is no cart" do
expect(helper).to receive(:current_monkey).and_return(nil)
expect(helper.current_monkey_banana_count).to eq 0
end
it "returns monkey.banana_count if there is a monkey" do
expect(helper).to receive(:current_monkey).and_return(Monkey.create!(banana_count: 5))
expect(helper.current_monkey_banana_count).to eq 5
end
end
end
Maybe you can achieve that by mocking current_monkey in this way (have you tried it already?):
RSpec.describe MonkeysHelper, type: :helper do
let(:monkey) { create(:monkey) }
before do
allow(helper).to receive(:current_monkey_user) { monkey }
end
# your rest of code
end
Cheers!
View can call helper methods defined in controller because controller eval them automatically, please check code here.
But your helper test doesn't call controller, so that current_monkey isn't available on MonkeysHelper module. The best practice is helpers defined in controller call helper defined in helper class but not vice versa. In your case, you can move current_monkey to MonkeyHelper to be able to test it.
I've got a method defined in ApplicationController as a helper method.
helper_method :can_access_participant_contact_data?
I'm trying to write a test for a helper method that resides in a helper file. This helper method makes a call to helper_method :can_access_participant_contact_data?
# In participants_helper.rb
#
def redacted_contact_data participant, attribute_name
attribute = participant.try(:contact_data).try(attribute_name)
return attribute if can_access_participant_contact_data?(participant)
return nil if attribute.blank?
return attribute.gsub(/\S/i, '*') # Asterisked string
end
All I'm doing so far in my test is making a call to redacted_contact_data
require 'test_helper'
class ParticipantsHelperTest < ActionView::TestCase
test "should return an asterisked string with spaces" do
redacted_contact_data(Participant.first, :name)
end
end
When I run my test, I'm getting this message
undefined method `can_access_participant_contact_data?' for #<ParticipantsHelperTest:0x007fd6c7c6d608>
I've been having a look around but I'm not sure how to get around this issue. Do I need to mock can_access_participant_contact_data? somehow? or can I just include the method into the test?
AFAIK (As far as I know), you cannot fix this without stubbing, or doing some change in your code, as essentially a helper file is just a module of itself that should be treated independent of where it's gonna be included. Who knows you might want to include such helper file inside your model files for example, in which incidentally the model file also has a method named can_access_participant_contact_data? but does differently from that one defined in the ApplicationController, therefore you cannot unit test this without specifying the context / base.
Possible Workarounds:
Stubbing:
Use Mocha or rework testing into RSpec
Or manually (maybe there's a better way) by:
test "should return an asterisked string with spaces" do
ParticipantsHelper.class_eval do
define_method :can_access_participant_contact_data? do |arg|
true
end
end
redacted_contact_data(Participant.first, :name)
end
Or, moving all your ApplicationController helper methods into a separate/existing helper file, say inside your already existing ApplicationHelper. Then afterwards, include that helper inside your other helper file that you are testing that is making use of the method/s. i.e.:
# helpers/application_helper.rb
module ApplicationHelper
def can_access_participant_contact_data?(participant)
# YOUR CODE
end
end
# helpers/participants_helper.rb
module ParticipantHelper
include ApplicationHelper
def redacted_contact_data participant, attribute_name
attribute = participant.try(:contact_data).try(attribute_name)
return attribute if can_access_participant_contact_data?(participant)
return nil if attribute.blank?
return attribute.gsub(/\S/i, '*') # Asterisked string
end
end
If using this approach, then two ways to call the helper method inside the controller:
Use Rails helpers method inside a controller:
class ParticipantsController
def show
helpers.can_access_participant_contact_data?(#participant)
end
end
Or, include the helpers directly (I personally prefer the other approach just above)
class ApplicationController < ActionController::Base
include ApplicationHelper
end
class ParticipantsController < ApplicationController
def show
can_access_participant_contact_data?(#participant)
end
end
For the view files, you won't need to update any code.
Another idea is to do "helper test" in "controller test" as follows:
require 'test_helper'
class ParticipantsControllerTest < ActionController::TestCase
setup do
# do some initialization here. e.g. login, etc.
end
test "should return an asterisked string with spaces" do
participant = ...
get :show, id: participant.id
assert_equal '...', #controller.view_context.redacted_contact_data(...)
end
end
Where, #controller is ParticipantsController object already defined by rails controller testing framework (or you can explicitly define it when controller name is different from *ControllerTest), and view_context is the object for helper methods (see https://api.rubyonrails.org/classes/ActionView/Rendering.html#method-i-view_context for more detail).
Helper method often refer controller object and/or method (like session, request) so that it is sometimes difficult to do unit-test only in test/helpers/*. This is the reason why I test helper in controller in such a case.
My AdminController looks like:
class AdminController < ApplicationController
before_action :check_admin
private
def check_admin
redirect_to 'home/error' unless current_user.admin?
end
end
In my rspec test, how can I test this if there are no route or views?
require 'rails_helper'
RSpec.describe AdminController, type: :controller do
context "with no render_views" do
it "redirects for non-admin users" do
#???expect do
end
end
end
I am assuming that you are using a before_action in your AdminController, even though this controller does not have any actions, so that any controllers that inherit from it will automatically by "admin only".
If so, there are two ways to approach testing this.
1) Don't write a test for check_admin.
Instead, write tests for any controller actions that you define later! For example, if you have the following controller in your application tomorrow:
UsersController < AdminController
def index
#users = User.all
end
end
then you can write the following specs for that controller.
describe UsersController
it 'redirects for non-admins' do
# insert the test you feel like writing here!
end
it 'renders the right template for admin users' do
magical_login_method
get :index
expect(response).to render_template(:index)
end
end
and so on!
2) Call the private method directly
This approach makes me feel a bit icky. Although this defeats the philosophy of public vs private methods, you can call a private method in ruby by using the .send method.
describe AdminController
it 'redirects for non-admins' do
# make an instance of your controller
controller = AdminController.new
# expect the controller to call `redirect_to`
expect(controller).to receive(:redirect_to).with('home/error')
# call the private `check_admin` method
controller.send(:check_admin)
end
end
Some, perhaps many, would argue that this sort of testing is highly intrusive, and may even limit the flexibility of your codebase in the future. I'd recommend approach 1, not because it's lazy, but because it tests things once there's something to test!
I have an auth method and want to put it in my application_controller.
class ApplicationController < ActionController::Base
helper_method :check_cred
def check_cred
"within check cred"
end
but if I do this
require 'spec_helper'
describe ApplicationController do
it 'should check_cred', task050: true do
check_cred.should == 'within check cred'
end
end
I get:
undefined local variable or method `check_cred' for #<RSpec::Core::ExampleGroup::Nested_9:0x007ff5e3e40558>
How would I call a method like this to test?
thx
RSpec controller specs wrap ActionController::TestCase::Behavior, which provides some instance variables to be used during tests:
Special instance variables
ActionController::TestCase will also automatically provide the following instance
variables for use in the tests:
#controller:
The controller instance that will be tested.
So you may be able to do the following:
it 'should check_cred', task050: true do
#controller.check_cred.should == 'within check cred'
end
Alternatively, you could move this helper method out into a separate helper module, and use an RSpec helper spec to perform the test, which may prove to be a better way to structure this test.