Rails How to test a method receives block argument with Rspec - ruby-on-rails

Lets say I have this in application controller:
class ApplicationController < ActionController::Base
def example(&block)
...code here
end
end
and this in another controller:
class OtherController < ApplicationController
def index
example { some_text("irrelevant_text") }
end
private
def some_text var
"#{var}_string"
end
end
I want to test that when calling the index action example method gets called with the irrelevant_text_string argument.
Something like:
describe 'index' do
it 'should call example' do
controller.should_receive(:example).with("irrelevant_text_string")
get :index
end
end
But this isn't working. I keep getting
received :example with unexpected arguments
expected: ("irrelevant_text_string")
got: (no args)
Why does it say no args?
Any help will be appreciated

Because you are passing a block to it, not an argument.

Related

How to test the callback for any actions in the controller using request.headers?

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.

Rails early return if before_action attribute set fails

I would like to perform a db lookup using the incoming request's remote_ip before any controller method is hit in order to set a particular controller class attribute. However if the lookup fails (if no object is linked to the request IP) I would like to immediately return a 404 response.
For example:
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
# or if no instance is found, immediately return a 404 without hitting "index" method (or any other method for that matter)
end
end
Any help will be greatly appreciated!
You can raise an ActiveRecord::RecordNotFound exception, which will stop the action and return a 404. Or you can render or redirect which will also stop the action. See Rails Filters docs. Here are examples of each.
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
raise ActiveRecord::RecordNotFound unless #instance # returns 404
end
end
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
render(status: 404, inline: "Instance not found") unless #instance
end
end

Dynamically add method in before(:all)

Im testing a Module that can be included in a Controller.
Right now the code looks like that:
class GithubAuthorizationController < ApplicationController
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
describe GithubAuthorizationController do
before(:all) do
#page_content = "lorem_ipsum"
end
...........
As you can see I basically create a Test-Controller before the tests are run. Now I would like to add the module and index method in the before(:all)-block. I tried:
class GithubAuthorizationController < ApplicationController
end
describe GithubAuthorizationController do
before(:all) do
#page_content = "lorem_ipsum"
class < #controller
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
end
...........
As I can see in debugger in the before(:all) block the #controller is defined as <GithubAuthorizationController .... So It is a instance. There Is also no error when running the code, but the tests fails, because of The action 'index' could not be found ...
What do I wrong? How can I move the code to the before(:all) block? Thanks
The way to do this in rspec is with a controller block:
describe GithubAuthorizationController, type: :controller do
context "with module" do
controller do
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
# within this block the controller will be the anonymous controller class created above
end
end
If you have set infer_base_class_for_anonymous_controllers to false (this is not the default) then you need to do controller(GithubAuthorizationController) or you'll inherit directly from ApplicationController
Your issue could be down to a missing route - the controller helper creates some routes for you (for the default index, show etc.) actions. You can add extra ones in an example with
it "does something with a custom route" do
routes.draw { get "custom" => "github_authorization#custom" }
get :custom
...
end

calling methods dynamically inside a controller

I have the following scenario
I want to add methods dynamically to a controller. All my method names are in a table . Please refer the following example
-table (method_names)-
1 - Walk
2 - Speek
3 - Run
and I have a controller
class UsersController < ApplicationController
def index
end
end
Inside this index action i want to call my methods dynamically. Those methods were actually implemented else ware.
I have another controller like
class ActionImplementController < ApplicationController
def walk
puts "I'm walking"
end
def speek
puts "I'm sppeking"
end
def run
puts "I'm running"
end
end
** I have done something like below and its working
class UsersController < ApplicationController
def index
a = eval("ActionImplementController.new.run")
end
end
But my question is , is this the right way or is there anyother way to do this
Thanks in advance
cheers
sameera
While the first answer works, i would prefer something like this
module ImplementsActions
def run
...
end
def walk
..
end
def ...
end
and then in your controller write
class UsersController < ActionController::Base
include ImplementsActions
# now you can just use run/speek/walk
def index
run
end
end
Much cleaner because the code can be shared, but it is defined where you need it.
I think it's generally best to avoid the use of eval. If you can, I would make all your methods class methods and then run them like so:
def index
ActionImplementController.send :run
# ActionImplementController.new.send(:run) works if you can't use class methods
end

How do you test a Rails controller method exposed as a helper_method?

They don't seem to be accessible from ActionView::TestCase
That's right, helper methods are not exposed in the view tests - but they can be tested in your functional tests. And since they are defined in the controller, this is the right place to test them. Your helper method is probably defined as private, so you'll have to use Ruby metaprogramming to call the method.
app/controllers/posts_controller.rb:
class PostsController < ApplicationController
private
def format_something
"abc"
end
helper_method :format_something
end
test/functional/posts_controller_test.rb:
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
test "the format_something helper returns 'abc'" do
assert_equal 'abc', #controller.send(:format_something)
end
end
This feels awkward, because you're getting around encapsulation by using send on a private method.
A better approach is to put the helper method in a module in the /controller/concerns directory, and create tests specifically just for this module.
e.g. in app controller/posts_controller.rb
class PostsController < ApplicationController
include Formattable
end
in app/controller/concerns/formattable.rb
module Concerns
module Formattable
extend ActiveSupport::Concern # adds the new hot concerns stuff, optional
def format_something
"abc"
end
end
end
And in the test/functional/concerns/formattable_test.rb
require 'test_helper'
# setup a fake controller to test against
class FormattableTestController
include Concerns::Formattable
end
class FormattableTest < ActiveSupport::TestCase
test "the format_something helper returns 'abc'" do
controller = FormattableTestController.new
assert_equal 'abc', controller.format_something
end
end
You could test #controller.view_context from your functional/controller tests. This method is available in Rails 3, 4, and 5, as far as I can tell.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user
# ...
end
test/controllers/application_controller_test.rb
require 'test_helper'
class ApplicationControllerTest < ActionController::TestCase
test 'current_user helper exists in view context' do
assert_respond_to #controller.view_context, :current_user
end
end
If you didn't want to test one of your controller subclasses, you could also create a test controller to verify that the method in the view_context is the same one from the controller and not from one of your view helpers.
class ApplicationControllerHelperTest < ActionController::TestCase
class TestController < ApplicationController
private
def current_user
User.new
end
end
tests TestController
test 'current_user helper exists in view context' do
assert_respond_to #controller.view_context, :current_user
end
test 'current_user returns value from controller' do
assert_instance_of User, #controller.view_context.current_user
end
end
Or, more likely, you'd want to be able to test the helper in the presence of a request.
class ApplicationControllerHelperTest < ActionController::TestCase
class TestController < ApplicationController
def index
render plain: 'Hello, World!'
end
end
tests TestController
def with_routing
# http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-with_routing
# http://guides.rubyonrails.org/routing.html#connecting-urls-to-code
super do |set|
set.draw do
get 'application_controller_test/test', to: 'application_controller_test/test#index'
end
yield
end
end
test 'current_user helper exists in view context' do
assert_respond_to #controller.view_context, :current_user
end
test 'current_user returns value from controller' do
with_routing do
# set up your session, perhaps
user = User.create! username: 'testuser'
session[:user_id] = user.id
get :index
assert_equal user.id, #controller.view_context.current_user.id
end
end
end
I've struggled with this for a bit, because the accepted answer didn't actually test whether the method was exposed as a helper method.
That said, we can use the #helpers method to get a proxy for testing.
For example:
class FooController < ApplicationController
private
def bar
'bar'
end
helper_method :bar
end
Can be tested with:
require 'test_helper'
class FooControllerTest < ActionController::TestCase
test 'bar is a helper method' do
assert_equal 'bar', #controller.helpers.bar
end
end
Indeed they're not. The view tests are specifically for the views. They don't load the controllers.
You should mock this method and make it return whatever is appropriate depending of your context.

Resources