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)
Related
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?
I want to create a custom variable similar to response object that should only be available in controller specs. I noticed that rspec supports filters which are before/after hooks which means I can create instance variables with them to be used later. But response object feels and works more like a let variable that is lazily evaluated. Also, controller specs support assign method that can accept arguments.
Does rspec support any way to create similar methods to be used with a specific type of spec?
Note: I don't need to support anything below rspec 3.0.
You can simply do this by creating a module with your function and then including that in your RSpec configure block. You can control the types of specs where this should be available as a second parameter when you include the module:
module ControllerSpecHelpers
def something
'fubar2000'
end
end
RSpec.configure do |config|
config.include ControllerSpecHelpers, type: :controller
end
RSpec.describe BlahController, type: :controller do
it 'should be possible to use the `something` helper in a controller spec' do
expect(something).to eq('fubar2000')
end
end
This is my module. I like to test method load_csv
I referred this example Example Link
and wrote this code. Below is the module Code
require 'csv'
module DummyModule
class Test
def load_csv(filename)
CSV.read(filename, :row_sep => "\r", :col_sep => "\t")
end
end
end
this is my Rspec
require 'spec_helper'
describe DummyModule do
let(:data) { "title\tsurname\tfirstname\rtitle2\tsurname2\tfirstname2\r" }
let(:result) { [["title", "surname", "firstname"], ["title2", "surname2", "firstname2"]] }
before(:each) do
#attribute_validator = TestAttributeValidator.new
end
it "should parse file contents and return a result" do
puts data.inspect
puts result.inspect
File.any_instance.stubs(:open).with("filename","rb") { StringIO.new(data) }
#attribute_validator.load_csv("filename").should eq(result)
end
end
class TestAttributeValidator
include DummyModule
end
It gives me this error
DummyModule should parse file contents and return a result
Failure/Error: #attribute_validator.load_csv("filename").should eq(result)
NoMethodError:
undefined method `load_csv' for #<TestAttributeValidator:0xd0befd>
# ./spec/extras/dummy_module_spec.rb:15:in `(root)'
Pls Help
You probably do not want your
class Test
inside your module definition. Like this the following would work:
#attribute_validator_tester = TestAttributeValidator::Test.new
#attribute_validator_tester.respond_to? 'load_csv'
=> true
but that is probably not what you intended. Including a module into a class will add all the 'features' of the module (that is all the methods, but also constants, and classes) to the class the module is included in. In your example you added the class Test to the namespace of class TestAttributeValidator and the instances of this class would have the method load_csvyou desire.
Just omit the class definition inside your module and all will be well.
(Adding another answer as this is really another question)
Googling for Errno::ENOENT leads to this answer, but that should hardly be necessary as the error message is really telling, your file was not found. Since you stubbed for "filename" it should be found if the version of CSV you are using still uses open to open the file (which the current ruby CSV reader seems to do, see the source) then it actually should work.
However depending on your version of ruby the CSV library might add some more options, the version I referenced merges universal_newline: false to the options for open, so your stub would not have all the parameters it expects and forward your call to the "regular" method which does not find your "filename". You should check your exact version of ruby and stub accordingly.
That is probably part of the legacy that comes with such a dynamic language as ruby :-)
How is it that rspec feature tests implicitly know to use methods such as find, within, and fill_in from the page object?
I've written a helper class for some of my rspec tests and wanted to use those methods, and realized that I needed to pass the page object into the method, and then use page.find and the like.
RSpec achieves this by including Capybara::DSL in those cases where it wants those methods available. The module is pretty elegant, if you want to take a look at https://github.com/jnicklas/capybara/blob/f83edc2a515a3a4fd80eef090734d14de76580d3/lib/capybara/dsl.rb
suppose you want to include the following module:
module MailerMacros
def last_email
ActionMailer::Base.deliveries.last
end
def reset_email
ActionMailer::Base.deliveries = []
end
end
to include them, just call config.include(MailerMacros), like this:
RSpec.configure do |config|
config.include(MailerMacros)
end
now, you should be able to call reset_email() & last_email instead of MailerMacros::reset_email().
In my app when user share something he's rating grows. When he tries to share something twice – he will get no additional rating for second try. For application, share callback is triggered by client-side with JS, so, it's just a regular GET-request. So, I need to test this functionality. It's easy. But I'v got several sections with this behavior. Every controller from that sections have method named "rating_from_share", so tests are pretty similar. I think it is good idea to extract that test's in a mixing and include them where it should be, but I can't figure out, how can I do this.
So, is it real to include a mixing with RSpec to a RSpec test? Maybe something kind of metaprogramming can solve this problem?
P.S. realization of "rating_from_share" method is not really the same but only the output result, so I can't to aggregate it to a superclass and test them here.
EDIT:
According to Vimsha answer, should I do something like this?
Module Share
def share
it 'should be fun'
expect(#fun.isFun?).toBe == 'yup' # the #fun is declared in ShareTest
end
end
end
describe "Share Test" do
extend Share
before :each do
#fun = Fun.new
end
it 'should do test' do
share # call method from Share module, which has real RSpec code?
end
end
The code is written just here, I'm just trying to get the idea.
A common practice in RSpec is to store such logic under spec/support. For instance:
# spec/support/ratings_macros.rb
module RatingsMacros
...
end
You then need to load it from your spec_helper:
# spec/spec_helper.rb
...
RSpec.configure do |config|
...
config.include RatingsMacros
You can now call in your tests all the methods defined in the RatingsMacros module.
You can use shared examples.
These are typically saved under spec/support and loaded via spec_helper.rb. Be sure to read the docs to understand how to load the shared code--it is not automagically performed for you.
Once they are defined you can include them like so:
# spec/support/decorated_model.rb
shared_examples "decorated_model" do
it "can be decorated" do
subject.should respond_to?(:decorate)
end
end
# my_class_spec.rb
describe MyClass do
it_behaves_like "decorated_model"
end
module Share
def share
end
end
describe "Share Test" do
extend Share
end
You can call the methods of the module directly within the tests
The other answers pollute the test with the module's methods, or involve writing a dummy class. This solution uses the built-in double object as a throwaway object to extend with the module's methods.
RSpec.describe Share do
describe '#share' do
subject { double.extend(described_class) }
end
it 'does something cool' do
expect(subject.share).to eq 'something_cool'
end
end