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
Related
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
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 try to test my controller concerns using minitest-rails and combining this techniques:
http://ridingtheclutch.com/post/55701769414/testing-controller-concerns-in-rails.
Anonymous controller in Minitest w/ Rails
And i get "no route matches error":
ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"fake"}
require "test_helper"
require "warden_mock"
class FakeController < ApplicationController
attr_accessor :request
def initialize(method_name=nil, &method_body)
include MyConcern # method redirect_to_404 placed here
#request = OpenStruct.new # mockup request
#request.env = {}
#request.env['warden'] = WardenMock.new # mockup warden
if method_name and block_given? # dynamically define action for concern methods testing
self.class.send(:define_method, method_name, method_body)
test_routes = Proc.new do
resources :fake
end
Rails.application.routes.eval_block(test_routes)
end
end
end
describe FakeController do # just very simple test
context "just redirect_to_404" do
it "it must redirect to /404" do
#controller = FakeController.new(:index) { redirect_to_404 }
get :index
assert_redirected_to '/404'
end
end
end
I have rails 4.1.5 and minitest 5.4.0
Probably too late for the OP, but I've done it in this way:
require 'test_helper'
class SolrSearchParamsFakeController < ApplicationController
include SolrSearchParams # this is my controller's concern to test
def index
# The concern modify some of the parameters, so I'm saving them in a
# variable for future test inspection, so YMMV here.
#params = params
render nothing: true
end
end
Rails.application.routes.draw do
# Adding a route to the fake controller manually
get 'index' => 'solr_search_params_fake#index'
end
class SolrSearchParamsFakeControllerTest < ActionController::TestCase
def test_index
get :index, search: 'asdf wert'
# finally checking if the parameters were modified as I expect
params = assigns(:params)
assert_equal params[:original_search], 'asdf wert'
end
end
Update
Sorry, but actually this is messing up all my tests that involve route access in some way, as with Rails.application.routes.draw I'm rewriting all the routes for tests and leaving just that solr_search_params_fake#index route.
Not sure how to add instead of rewriting.... a fix would be adding directly to config/routes.rb my test routes with an if Rails.env.test? condition? (yeah, it's a crappy solution, but I'll leave this here in case someone find a better way to do this).
I keep getting
#controller is nil: make sure you set it in your test's setup method.
When I run my tests. Any idea what this means?
when you inherit from ActionController::TestCase it infers the controller name from the test name if they do not match you have to use the setup part of test to set it.
So if you have
class PostsControllerTest < ActionController::TestCase
def test_index
#assert something
end
end
Then #controller is auto instantiated to PostsController, however, if this were not the case and you had a different name you would need a setup as such
class SomeTest < ActionController::TestCase
def setup
#controller = PostController.new
end
end
I was in the process of upgrading to rspec 3 from the beta version on rails 4 and ran into this error. The problem turned out to be that our Controller spec describe statements used symbols instead of strings. Rspec was attempting to instantiate the symbol as the controller but they were in fact 'actions'.
#trys to set #controller = Index.new
describe SomeController do
describe :index do
before do
get :index, format: :json
end
it { expect(response).to be_success}
end
end
#works
describe SomeController do
describe 'index' do
before do
get :index, format: :json
end
it { expect(response).to be_success}
end
end
ErsatzRyan answer is correct, however there is a small typo. Instead of
#controller = PostsController
it should be
#controller = PostsController.new
otherwise you get an error: undefined method `response_body='
Check whether you are ending the do and end properly.
RSpec.describe LeadsController, type: :controller do
# All tests should come here
end
If the names match, and the #controller variable is still nil, try checking for errors in the controller instantiation. For me I had a controller initialize method that had a bug in it. For some reason the controller was just nil in the test, rather than throwing an error when it wasn't instantiated.
Or, you can simply do this:
RSpec.describe PostsControllerTest, :type => :controller do
# ...
end
In Rails 6 looks like it is like this:
tests PostController
https://www.rubydoc.info/docs/rails/4.1.7/ActionController/TestCase#label-Controller+is+automatically+inferred
I encountered this error because I surrounded the controller name in quotes.
# broken
RSpec.describe 'RegistrationsController', type: :controller do
...
end
# works
RSpec.describe RegistrationsController, type: :controller do
...
end
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