How to include methods of models in RSpec test cases - ruby-on-rails

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.

Related

Unit testing code that references Rails models without loading the models

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.

How to test in Ruby/Rails if method has been called in class body?

I can't figure how to test class method calls from within the class body.
How can I test it?
class User
act_as_paranoid
end
it 'is called from class body' do
expect(User).to receive(:acts_as_paranoid)
User.new
end
It's usually recommended to test the behavior, not the implementation. In this case, whatever acts_as_paranoid provides for this class in terms of behavior, is what you want to test.
However, if you trust that calling acts_as_paranoid correctly provides all the behavior you need and just want to test that it is added to the class, you can use:
assert User.included_modules.include? ActsAsParanoid::Core
To figure this out I just briefly looked at the source code for acts_as_paranoid here: https://github.com/ActsAsParanoid/acts_as_paranoid/blob/master/lib/acts_as_paranoid.rb#L8
You can see that on line 50, it extends the ActsAsParanoid module to ActiveRecord::Base, which gives the model classes access to the acts_as_paranoid method. And if you look at the definition of this method, you can see it calls include ActsAsParanoid::Core
Updated
This is not the greatest way to do this but if you must this is closer to what you want:
describe 'Check if a string method is in a file' do
it 'matches a string pattern' do
lines = File.read('user.rb').split("\n")
assert lines[1][/\b+acts_as_paranoid/]
#hacky way to make sure you don't accidentally comment it out
assert lines[1].split('#').count == 1
end
end
Original answer:
There is nothing here to test. Your class definition is invalid unless your method is defined when user.rb file loads. That is core ruby. Prove it.
#user_spec.rb
require 'minitest/autorun'
require_relative 'user'
describe 'User' do
it 'is a valid class' do
assert User
end
end
#user.rb
class User
acts_as_paranoid
end
If acts_as_paranoid is not defined before ruby loads user.rb, spec fails as soon as the file is required. If this is all the code you have this test fails. Comment out acts_as_paranoid test will pass.
To just test that you added acts_as_paranoid to User, you can do:
it 'has acts_as_paranoid' do
expect(User).to respond_to(:acts_as_paranoid)
end

Rails and RSpec: Testing controllers with the same name in different namespace (module)

I have rails 4.1.16 API application that is tested using RSpec 3.4.0, and I experience problems with testing classes called the same name in a different module.
The structure is:
app/controllers/bar/notifications_controller.rb
class Bar::NotificationsController < ApiController
...
end
and controller with the same name in a different module:
app/controllers/foo/bar/notifications_controller.rb
module Foo
class Bar::NotificationsController < ApiController
...
end
end
The Foo is a new module and does not have tests yet.
After adding it, all the corresponding controller tests for the old Bar::NotificationsController started to fail.
The spec file:
spec/controllers/bar/notifications_controller_spec.rb
require 'spec_helper'
describe Bar::NotificationsController, type: :controller do
...
end
All the tests in that spec file fail with the same error:
RuntimeError:
#controller is nil: make sure you set it in your test's setup method.
The problem does not exist when I change the controller name in the Foo module:
app/controllers/foo/bar/foo_notifications_controller.rb
module Foo
class Bar::FooNotificationsController < ApiController
...
end
end
I already tried adding on top of the spec file require 'bar/notifications_controller' and using the class name as a string describe "Bar::NotificationsController, type: :controller but it did not solve the issue (the same error).
Why is this happening? What is the solution?
I want to believe there is a tiny thing I did not try yet and I don't have to pollute my code and the structure with nonsense names just to make the specs pass.
Many thanks in advance for your help!
In general, I've take to including all namespacing in the class definition. Something like:
app/controllers/foo/bar/notifications_controller.rb
class Foo::Bar::NotificationsController < ApiController
...
end
While, at first glance, this might look the same as:
app/controllers/foo/bar/notifications_controller.rb
module Foo
class Bar::NotificationsController < ApiController
...
end
end
These are, in fact, different. The difference is in how Rails handles autoloading of constants. I won't go into the details here because it's a longer topic and there are good articles/posts out in the web-o-sphere.
You can find good articles on how Rails handles autoloading like this one (or try Googling rails constant loading)
Also, as the article notes, Ruby constant loading operates differently than Rails loading. Good information on Ruby constant loading can be found here (or try Googling ruby constant loading).

Rspec Controller testing in Rails a controller that inherits from AbstractController::Base

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?

How to Test a Concern in Rails

Given that I have a Personable concern in my Rails 4 application which has a full_name method, how would I go about testing this using RSpec?
concerns/personable.rb
module Personable
extend ActiveSupport::Concern
def full_name
"#{first_name} #{last_name}"
end
end
The method you found will certainly work to test a little bit of functionality but seems pretty fragile—your dummy class (actually just a Struct in your solution) may or may not behave like a real class that includes your concern. Additionally if you're trying to test model concerns, you won't be able to do things like test the validity of objects or invoke ActiveRecord callbacks unless you set up the database accordingly (because your dummy class won't have a database table backing it). Moreover, you'll want to not only test the concern but also test the concern's behavior inside your model specs.
So why not kill two birds with one stone? By using RSpec's shared example groups, you can test your concerns against the actual classes that use them (e.g., models) and you'll be able to test them everywhere they're used. And you only have to write the tests once and then just include them in any model spec that uses your concern. In your case, this might look something like this:
# app/models/concerns/personable.rb
module Personable
extend ActiveSupport::Concern
def full_name
"#{first_name} #{last_name}"
end
end
# spec/concerns/personable_spec.rb
require 'spec_helper'
shared_examples_for "personable" do
let(:model) { described_class } # the class that includes the concern
it "has a full name" do
person = FactoryBot.build(model.to_s.underscore.to_sym, first_name: "Stewart", last_name: "Home")
expect(person.full_name).to eq("Stewart Home")
end
end
# spec/models/master_spec.rb
require 'spec_helper'
require Rails.root.join "spec/concerns/personable_spec.rb"
describe Master do
it_behaves_like "personable"
end
# spec/models/apprentice_spec.rb
require 'spec_helper'
describe Apprentice do
it_behaves_like "personable"
end
The advantages of this approach become even more obvious when you start doing things in your concern like invoking AR callbacks, where anything less than an AR object just won't do.
In response to the comments I've received, here's what I've ended up doing (if anyone has improvements please feel free to post them):
spec/concerns/personable_spec.rb
require 'spec_helper'
describe Personable do
let(:test_class) { Struct.new(:first_name, :last_name) { include Personable } }
let(:personable) { test_class.new("Stewart", "Home") }
it "has a full_name" do
expect(personable.full_name).to eq("#{personable.first_name} #{personable.last_name}")
end
end
Another thought is to use the with_model gem to test things like this. I was looking to test a concern myself and had seen the pg_search gem doing this. It seems a lot better than testing on individual models, since those might change, and it's nice to define the things you're going to need in your spec.
The following worked for me. In my case my concern was calling generated *_path methods and the others approaches didn't seem to work. This approach will give you access to some of the methods only available in the context of a controller.
Concern:
module MyConcern
extend ActiveSupport::Concern
def foo
...
end
end
Spec:
require 'rails_helper'
class MyConcernFakeController < ApplicationController
include MyConcernFakeController
end
RSpec.describe MyConcernFakeController, type: :controller do
context 'foo' do
it '' do
expect(subject.foo).to eq(...)
end
end
end
just include your concern in spec and test it if it returns the right value.
RSpec.describe Personable do
include Personable
context 'test' do
let!(:person) { create(:person) }
it 'should match' do
expect(person.full_name).to eql 'David King'
end
end
end

Resources