I am working on a Rails 2 legacy project. I have MyController in Foo module:
module Api::Foo
class MyController < ::ActionController::Base
def doJob
puts "do the job"
end
end
end
In routes.rb I have:
map.connect 'api/foo/dojob', :controller => 'api/foo/my',:action => 'doJob', :conditions => { :method => :post }
I have another controller, which is outside the Foo module, it is in Bar module:
module Api::Bar
class AnotherController < ::ActionController::Base
def doCalculation
puts "do the calculation"
end
end
end
In unit test of AnotherControllerTest, how can I post to endpoint api/foo/dojob ?
class Api::Bar::AnotherControllerTest < ActionController::TestCase
def setup
end
test 'test internal api call' do
# how to post to "api/foo/dojob"??
end
end
I tried post "/api/foo/dojob",{}, it doesn't work.
You can't. ActionController::TestCase abstracts out the whole step of actually performing a actual HTTP request.
What ActionController::TestCase does when you do get :foo is create an instance of Api::Bar::AnotherController and pass it a mocked request. It then calls the #foo method on the controller. All this seemed like a great idea at the time since it lets you poke around in the controllers internals and was a bit faster.
It derives the class from the name of the test case class. While you could possibly override this to instantiate some other controller its the wrong answer to the wrong problem. The whole idea of a unit test is that it tests one component in isolation.
To do integration testing where you test multiple controllers you want ActionController::IntegrationTest. An integration test actually sends a HTTP request to a test server. This is also a more future proof testing strategy since controller tests have been axed.
require 'test_helper'
class ApiFlowsTest < ActionController::IntegrationTest
def setup
end
test 'test internal api call' do
post "api/foo/dojob", { foo: 'bar' }
get "api/foo/bar"
assert_response :success
end
end
Related
Simple example that by all accounts should work:
require 'test_helper'
class FoosController < ApplicationController
def index
render plain: 'something'
end
end
class UsersControllerTest < ActionDispatch::IntegrationTest
def test_some_routing
with_routing do |set|
set.draw do
get '/foos' => 'foos#index'
end
get '/foos'
assert_equal 200, response.status
end
end
end
Instead I'm getting: ActionController::RoutingError: No route matches [GET] "/foos"
What am I doing wrong? This is latest Rails 4.1 btw.
For those who come here seeking a solution to test their controller concerns in isolation (like I did), it may pay to use setup and teardown blocks instead of littering your code with with_routing blocks:
require 'test_helper'
class ConcernedController < ApplicationController
include Concern
def action
render plain: "response", status: :ok
end
end
class ConcernTest < ActionDispatch::IntegrationTest
setup do
#controller = ConcernedController.new
Rails.application.routes.draw do
get "/action" => "concerned#action"
post "/action" => "concerned#action"
end
end
teardown do
Rails.application.reload_routes!
end
test "concern method" do
post "/action"
# Test what the concern does
assert_response :ok
assert_equal "Response", response.body
end
end
Tested against Rails 5.0.2.
Ok, so I'm answering my own questions again. Problem is that with_routing doesn't work at all inside integration tests. Oversight or something, not sure. It only works for controller tests. So here's the work around:
class ActionDispatch::IntegrationTest
def with_routing(&block)
yield ComfortableMexicanSofa::Application.routes
ensure
ComfortableMexicanSofa::Application.routes_reloader.reload!
end
end
Basically you will redefine your application routes within that block and then reload them back from the routes.rb file.
-- edited --
We can have a block with new routes by defining a new method like:
def with_foo_route
Rails.application.routes.draw do
get 'foo' => 'foo#index'
end
yield
Rails.application.routes_reloader.reload!
end
And then execute a get requests to this new route by:
with_foo_route do
get '/foo'
assert_equal 200, status
end
I'm using minitest in Rails to do testing, but I'm running into a problem that I hope a more seasoned tester can help me out with because I've tried looking everywhere for the answer, but it doesn't seem that anyone has run into this problem or if they have, they opted for an integration test.
Let's say I have a controller called Foo and action in it called bar. So the foo_controller.rb file looks like this:
class FooController < ApplicationController
def bar
render 'bar', :layout => 'application'
end
end
The thing is that I don't want people to access the "foo/bar" route directly. So I have a route that is get 'baz' => 'foo#bar'.
Now I want to test the FooController:
require 'minitest_helper'
class FooControllerTest < ActionController::TestCase
def test_should_get_index
get '/baz'
end
end
But the test results in an error that No route matches {:controller=>"foo", :action=>"/baz"}. How do I specify the controller for the GET request?
Sorry if this is a dumb question. It's been very hard for me to find the answer.
Try to test thus:
With Rails 3:
require 'minitest_helper'
class FooControllerTest < ActionController::TestCase
def test_should_get_index
assert_routing "/baz", :controller => "foo", :action => "bar"
end
end
With Rails 4:
require "test_helper"
class FooControllerTest < ActionDispatch::IntegrationTest
def test_should_get_index
assert_generates "/baz", :controller => "foo", :action => "bar"
end
end
What is the best way to handle testing of concerns when used in Rails 4 controllers? Say I have a trivial concern Citations.
module Citations
extend ActiveSupport::Concern
def citations ; end
end
The expected behavior under test is that any controller which includes this concern would get this citations endpoint.
class ConversationController < ActionController::Base
include Citations
end
Simple.
ConversationController.new.respond_to? :yelling #=> true
But what is the right way to test this concern in isolation?
class CitationConcernController < ActionController::Base
include Citations
end
describe CitationConcernController, type: :controller do
it 'should add the citations endpoint' do
get :citations
expect(response).to be_successful
end
end
Unfortunately, this fails.
CitationConcernController
should add the citations endpoint (FAILED - 1)
Failures:
1) CitationConcernController should add the citations endpoint
Failure/Error: get :citations
ActionController::UrlGenerationError:
No route matches {:controller=>"citation_concern", :action=>"citations"}
# ./controller_concern_spec.rb:14:in `block (2 levels) in <top (required)>'
This is a contrived example. In my app, I get a different error.
RuntimeError:
#routes is nil: make sure you set it in your test's setup method.
You will find many advice telling you to use shared examples and run them in the scope of your included controllers.
I personally find it over-killing and prefer to perform unit testing in isolation, then use integration testing to confirm the behavior of my controllers.
Method 1: without routing or response testing
Create a fake controller and test its methods:
describe MyControllerConcern do
before do
class FakesController < ApplicationController
include MyControllerConcern
end
end
after do
Object.send :remove_const, :FakesController
end
let(:object) { FakesController.new }
it 'my_method_to_test' do
expect(object).to eq('expected result')
end
end
Method 2: testing response
When your concern contains routing or you need to test for response, rendering etc... you need to run your test with an anonymous controller. This allow you to gain access to all controller-related rspec methods and helpers:
describe MyControllerConcern, type: :controller do
controller(ApplicationController) do
include MyControllerConcern
def fake_action; redirect_to '/an_url'; end
end
before do
routes.draw {
get 'fake_action' => 'anonymous#fake_action'
}
end
describe 'my_method_to_test' do
before do
get :fake_action
end
it do
expect(response).to redirect_to('/an_url')
end
end
end
As you can see, we define the anonymous controller with controller(ApplicationController). If your test concerne another class than ApplicationController, you will need to adapt this.
Also for this to work properly you must configure the following in your spec_helper.rb file:
config.infer_base_class_for_anonymous_controllers = true
Note: keep testing that your concern is included
It is also important to test that your concern class is included in your target classes, one line suffice:
describe SomeTargetedController do
it 'includes MyControllerConcern' do
expect(SomeTargetedController.ancestors.include? MyControllerConcern).to be(true)
end
end
Simplifying on method 2 from the most voted answer.
I prefer the anonymous controller supported in rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller
You will do:
describe ApplicationController, type: :controller do
controller do
include MyControllerConcern
def index; end
end
describe 'GET index' do
it 'will work' do
get :index
end
end
end
Note that you need to describe the ApplicationController and set the type in case this does not happen by default.
My answer may look bit more complicated than these by #Benj and #Calin, but it has its advantages.
describe Concerns::MyConcern, type: :controller do
described_class.tap do |mod|
controller(ActionController::Base) { include mod }
end
# your tests go here
end
First of all, I recommend the use of anonymous controller which is a subclass of ActionController::Base, not ApplicationController neither any other base controller defined in your application. This way you're able to test the concern in isolation from any of your controllers. If you expect some methods to be defined in a base controller, just stub them.
Furthermore, it is a good idea to avoid re-typing concern module name as it helps to avoid copy-paste errors. Unfortunately, described_class is not accessible in a block passed to controller(ActionController::Base), so I use #tap method to create another binding which stores described_class in a local variable. This is especially important when working with versioned APIs. In such case it is quite common to copy large volume of controllers when creating a new version, and it's terribly easy to make such a subtle copy-paste mistake then.
I am using a simpler way to test my controller concerns, not sure if this is the correct way but seemed much simpler that the above and makes sense to me, its kind of using the scope of your included controllers. Please let me know if there are any issues with this method.
sample controller:
class MyController < BaseController
include MyConcern
def index
...
type = column_type(column_name)
...
end
end
my controller concern:
module MyConcern
...
def column_type(name)
return :phone if (column =~ /phone/).present?
return :id if column == 'id' || (column =~ /_id/).present?
:default
end
...
end
spec test for concern:
require 'spec_helper'
describe SearchFilter do
let(:ac) { MyController.new }
context '#column_type' do
it 'should return :phone for phone type column' do
expect(ac.column_type('phone')).to eq(:phone)
end
it 'should return :id for id column' do
expect(ac.column_type('company_id')).to eq(:id)
end
it 'should return :id for id column' do
expect(ac.column_type('id')).to eq(:id)
end
it 'should return :default for other types of columns' do
expect(ac.column_type('company_name')).to eq(:default)
end
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.
I'm using rails 2.2.2 and wondering how can I set the params values to test my helper methods.
I found some examples to let you run tests with helper methods but it doesn't work for me when I use the request or params value directly in the method.
require 'test_helper'
class ProductHelperTest < Test::Unit::TestCase
include ProductHelper
context 'ProductHelper' do
should 'build the link' do
assert_equal '', build_link
end
end
end
When using the request or params value I'll get an error that the local variable or method is undefined. How would I go about setting the value?
Error from shoulda when using the request value and it will be the same messages when using the params value.
1) Error:
test: ProductHelper should build the link. (ProductHelperTest):
NameError: undefined local variable or method `request` for #<ProductHelperTest:0x33ace6c>
/vendor/rails/actionpack/lib/action_controller/test_process.rb:471:in `method_missing`
/app/helpers/products_helper.rb:14:in `build_link`
./test/unit/product_helper_test.rb:10:in `__bind_1251902384_440357`
/vendor/gems/thoughtbot-shoulda-2.0.5/lib/shoulda/context.rb:254:in `call`
/vendor/gems/thoughtbot-shoulda-2.0.5/lib/shoulda/context.rb:254:in `test: ProductHelper should build the link. `
/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `__send__`
/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `run`
I guess you have to mock out calls to request and params using mocha or by defining mock objects in your test:
# Assuming ProductHelper implementation
module ProductHelper
def build_link
"#{request.path}?#{params[:id]}"
end
end
class ProductHelperTest < Test::Unit::TestCase
include ProductHelper
# mock by defining a method
def params
{ :controller => "test", :id => "23" }
end
# mock request.path using `mocha` # => "my_url"
def request
mock(:path => "my_url")
end
context 'ProductHelper' do
should 'build the link' do
assert_equal 'my_url?23', build_link
end
end
end
I hope this helps :)
As a note, if you are using Rails 2.3.x or anything which uses ActionView::TestCase - then all you really need to do is just have a private params method defined in your test.
e.g
require 'test_helper'
class BookmarksHelperTest < ActionView::TestCase
context "with an applied filter of my bookmarks" do
setup do
expects(:applied_filters).returns({:my_bookmarks => true})
end
should "not be able to see it when other filters are called using my_bookmarks_filter" do
assert_equal other_filters(:my_bookmarks), {}
end
end
private
def params
{}
end
end
You could even do one better by defining params as a method inside of ActionView::TestCase
This also works:
controller.params = {:filter => {:user_id => 1}
Here is an example:
require 'test_helper'
class BookmarksHelperTest < ActionView::TestCase
def test_some_helper_method
controller.params = {:filter => {:user_id => 1}
#... Rest of your test
end
end