I am writing controller tests for an application that I did not build, so it's definitely been a learning process. This is my first time encountering a controller that inherits directly from AbstractController::Base. It does not behave, obviously, the same as other controllers.
Its format is roughly:
class SchwadGenericController < AbstractController::Base
def schwad_method var_one, var_two = nil, var_three = nil
if var_two.blank?
var_one.generic_method
end
render template: "schwad_templates/generic_template", layout: false
end
end
I tried normal testing, this is where I am currently at to get ANYTHING to happen.
require 'rails_helper'
describe SchwadGenericController do
# before(:each) do
# SchwadGenericController.skip_authorize_resource
# end
# login_user
let!(:variable){ create(:my_factory_variable) }
describe 'controller methods' do
it 'should hit this method' do
binding.pry
SchwadGenericController.schwad_method(variable)
# expect(response).to_render template: "schwad_templates/generic_template"
end
end
end
And here is roughly where my failures are landing.
Failures:
1) SchwadGenericController controller methods should hit this method
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `request=' for # <SchwadGenericController:0x007f8022db0a20>
I read up on abstract controllers and their role in rails here: https://www.mobomo.com/2012/06/and-you-thought-render-farms-were-just-for-pixar/
I read up on the docs here: http://api.rubyonrails.org/classes/AbstractController/Base.html
I would really appreciate another set of eyes on this and guidance as to how you guys have tested controllers and their methods, with controllers that are inheriting from AbstractController::Base.... What am I missing?
-Schwad
After some testing, I don't think this is possible. Controller specs are just wrappers for Rails functional tests which test classes inheriting from ActionController::Base. For controller tests to even run, the controller must support the request and response objects, which is not the case of AbstractController::Base (these are defined in ActionController::Base). That is why you get the particular error when you run the test. For the same reason, you will not be able to use the controller spec helpers (expects) such as to_render because, again, they are defined only for controller specs and your controller class is not a "controller" in the "controller specs" sense.
The only option you seem to have for testing is to test the controller just as any other plain ruby class. You'd need to move your test out of the spec/controllers directory to some other, e.g. spec/abstract_controllers and then you'd have to give up all controller spec helpers and test just calling the instance methods, e.g.:
describe 'controller methods' do
it 'should hit this method' do
c = SchwadGenericController.new
expect(c).to receive(:render).with(template: "schwad_templates/generic_template", layout: false)
c.schwad_method(variable)
end
end
Extending directly from AbstractController::Base seems the likely source of the error to me. Unless you're doing something very nonconventional there should be no reason to do this.
Are you sure you don't intend to inherit from ActionController::Base? There's a whole bunch of modules in ActionController required for rendering which is probably explains the error on a missing method in your tests.
If switching to ActionController::Base doesn't work. Try running app.get "/path/to/action" from the rails console. Do you get the same error?
Related
I am trying to unit test a Plain Old Ruby Object that has a method which calls a class method on a Rails model. The Rails app is quite large (10s of seconds to load) so I'd prefer to avoid loading all of Rails to do my unit test which should run in under 1s.
Example:
class Foo
def bar
SomeRailsModel.quxo(3)
end
end
RSpec.describe Foo do
let(:instance) { Foo.new }
it 'calls quxo on SomeRailsModel' do
expect(SomeRailsModel).to receive(:quxo)
instance.bar
end
end
The problem here is that I need to require 'rails_helper' to load up Rails in order for app/models/some_rails_model to be available. This leads to slow unit tests due to Rails dependency.
I've tried defining the constant locally and then using regular spec_helper which kind of works.
Example:
RSpec.describe Foo do
let(:instance) { Foo.new }
SomeRailsModel = Object.new unless Kernel.const_defined?(:SomeRailsModel)
it 'calls quxo on SomeRailsModel' do
expect(SomeRailsModel).to receive(:quxo)
instance.bar
end
end
This code lets me avoid loading all of Rails and executes very fast. Unfortunately, by default (and I like this) RSpec treats the constant as a partial double and complains that my SomeRailsModel constant doesn't respond to the quxo message. Verifying doubles are nice and I'd like to keep that safety harness. I can individually disable the verification by wrapping it in a special block defined by RSpec.
Finally, the question. What is the recommended way to have fast unit tests on POROs that use Rails models without requiring all of Rails while also keeping verifying doubles functionality enabled? Is there a way to create a "slim" rails_helper that can just load app/models and the minimal subset of ActiveRecord to make the verification work?
After noodling a few ideas with colleagues, here is the concensus solution:
class Foo
def bar
SomeRailsModel.quxo(3)
end
end
require 'spec_helper' # all we need!
RSpec.describe Foo do
let(:instance) { Foo.new }
let(:stubbed_model) do
unless Kernel.const_defined?("::SomeRailsModel")
Class.new { def self.quxo(*); end }
else
SomeRailsModel
end
end
before { stub_const("SomeRailsModel", stubbed_model) }
it 'calls quxo on SomeRailsModel' do
expect(stubbed_model).to receive(:quxo)
instance.bar
end
end
When run locally, we'll check to see if the model class has already been defined. If it has, use it since we've already paid the price to load that file. If it isn't, then create an anonymous class that implements the interface under test. Use stub_const to stub in either the anonymous class or the real deal.
For local tests, this will be very fast. For tests run on a CI server, we'll detect that the model was already loaded and preferentially use it. We get automatic double method verification too in all cases.
If the real Rails model interface changes but the anonymous class falls behind, a CI run will catch it (or an integration test will catch it).
UPDATE:
We will probably DRY this up a bit with a helper method in spec_helper.rb. Such as:
def model_const_stub(name, &blk)
klass = unless Kernel.const_defined?('::' + name.to_s)
Class.new(&blk)
else
Kernel.const_get(name.to_s)
end
stub_const(name.to_s, klass)
klass
end
# DRYer!
let(:model) do
model_const_stub('SomeRailsModel') do
def self.quxo(*); end
end
end
Probably not the final version but this gives a flavor of our direction.
I'm writing some Rspec tests with capybara , and as part of that I need some methods of model.
I have created my model as:
class MyModel < ActiveRecord::Base
def method_name
#some stuff..
end
end
Now, I want to use MyModel in my Rspec test cases.
I tried to includeconfig.include Models in spec_helper.rb but it throws error
Uninitialized constant Models
And when I tried to include
include MyModel.new.method_name()
it throws error `include': wrong argument type nil (expected Module) (TypeError)
Without including any model class it runs test cases, but then my test cases are useless.
Here is my Rspec test case
require 'spec_helper'
describe "State Agency Page" do
let(:state_data) { FactoryGirl.build(:state_data) }
require 'mymodel.rb'
before {visit state_modifier_path}
it "should have breadcrumb", :js=>true do
page.should have_css('.breadcrumb')
end
end
Please provide any solution.
Thanks in advance.
I don't know what you mean by "your test cases are useless", but you seem to misunderstand the role of Ruby's include method.
With Rails, or the use of the rspec-rails gem with RSpec, your classes will be autoloaded when you reference the corresponding class constant (e.g. MyModel). So there generally is no need to do manual "loading" of individual models. Just make sure you have require 'spec_helper' at the beginning of your specs.
As for the errors you were getting with your attempts to use include, I suggest you find and read a Ruby reference to understand the semantics of the include method and why each attempt of yours failed in the way it did.
I wrote a gem for Rails that extends ApplicationController with a certain method. This method parses the current URL and uses the result to do a lookup. It looks something like this (simplified):
#current_account = Account.where(subdomain => request.subdomains.first).first
I want to include a test in the gem that asserts that the subdomain is looked up correctly based on a given URL.
I am running into two problems trying to write the test:
1) Since i'm testing within a gem, there is no controller (or Rails app for that matter) so I don't actually know where to start (Unit test, Controller test?)
2) I have searched everywhere, but I cannot find a way to setup the request hash in Rspec for testing. I would expect I would be able to do something like request.url = 'account1.example.com'
Any help on how to setup a proper test for this situation on Rspec is highly appreciated
If you are doing a controller test, then the URLs are usually specified on the configuration, like:
config.action_controller.default_url_options = { host: 'www.test.host' }
that is, if you're testing this as a rails application.
To test that a method in an abstract class works, your best option is to create a Test Subclass, and test using that. Something like
class TestController < ApplicationController; end
and then do your specs around this controller, which should behave exactly as an ApplicationController
EDIT
This would be an example of what I am proposing:
class TestController < ApplicationController
def index
render text: 'fake page' #This is so the action does not fail
end
end
describe TestController do
it 'searches for the current account in the right subdomain' do
Account.should_receive(:where).with({subdomain: 'www'})
get :index
end
end
Im trying to define some controller macros for Rspec. Im using rails 3 and have my macros defined in spec/support/macros/controller_macros.rb, that file looks like this:
module ControllerMacros
def self.login_admin
#code
end
end
in my spec helper I have:
config.include(ControllerMacros, :type => :controller)
So in my controller spec i just call login_admin in my admin tests but when ever i use the method i get
undefined local variable or method `login_admin' for #<Class:0xb6de4854> (NameError)
At first I assumed that controller_macros.rb wasn't being included but when I added a "puts" to the file but that showed the file was at least being executed.
I can't see anything wrong with my setup and copying the login_admin method into the describe block works fine so im not sure whats wrong with it.
Maybe I am late to that, but for new comers.
Here is a good examples of using macros:
http://osmose.6spot.com.br/2011/01/rails-resource-routing-spec-w-rspec/
when you include a module it's methods are visible inside examples.
But when you extend the module, it's methods are only visible outside examples.
It gives you ways to compose your macros for each situation.
Try
ControllerMacros.login_admin
or remove self from the method definition.
One line answer: Remove self from the method definition
Why? The methods of included modules are available in RSpec examples
The login_admin method defined in ControllerMacros will be available in your RSpec example as login_admin
To Be Specific:
Rewrite spec/support/macros/controller_macros.rb as
module ControllerMacros
def login_admin
#code
end
end
Then tell Rspec to include the Macros
config.include(ControllerMacros, :type => :controller)
I must be missing something very simple, but can't find the answer to this. I have a method named foo inside bar_controller. I simply want to call that method from inside a functional test.
Here's my controller:
class BarsController < ApplicationController
def foo
# does stuff
end
end
Here's my functional test:
class BarsControllerTest << ActionController::TestCase
def "test foo" do
# run foo
foo
# assert stuff
end
end
When I run the test I get:
NameError: undefined local variable or method `foo' for #<BarsControllerTest:0x102f2eab0>
All the documentation on functional tests describe how to simulate a http get request to the bar_controller which then runs the method. But I'd just like to run the method without hitting it with an http get or post request. Is that possible?
There must be a reference to the controller object inside the functional test, but I'm still learning ruby and rails so need some help.
I found the answer in "Agile Web Development with Rails" Book. ActionController::TestCase initializes three instance variables needed by every functional test: #controller (contains instance of controller under test), #request, and #response.
You need call this action with HTTP verb like
get :foo
post :foo
etc...