Application Helper Spec, test controller-specific output? - ruby-on-rails

I'm a little unsure as to how to write a test for a helper method which has output that is based on the given controller and action that are requested.
Given the following helper method in my application helper...
module ApplicationHelper
def body_id
[ controller.controller_name, controller.action_name ].join("_")
end
end
... how would you write an application_helper_spec that tests this method?

Assign or mock the controller object. That will give you something to test against. (RSpec includes a very good mocking/stubbing library.)

rspec-rails has some built sugar for testing helpers as #marnen-laibow-koser mentioned. But I sometimes like to write very lightweight tests for my helpers that don't have to load in my whole rails environment. This way the tests can run in less than a second as opposed to the multiple seconds it takes to load the rails environment.
Here is an example:
require 'spec_helper_lite'
require_relative '../../app/helpers/application_helper'
describe ApplicationHelper do
let(:helper) do
class Helper
include ApplicationHelper
end
Helper.new
end
it "formats an elapsed time as a number of minutes and seconds" do
helper.elapsed_as_min_sec(90).should == "1min 30sec"
end
end
And my spec_helper_lite.rb file just looks like this:
require 'rspec'
require 'rspec/autorun'
RSpec.configure do |config|
config.order = "random"
end

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.

RSpec page variable

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().

RSpec: how can I mixin a module into RSpec test?

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

How to test a Rails helper that depends on another Rails helper?

I am writing an RSpec spec to test a Rails helper. The problem is that the helper method that I'm testing depends on a method defined in a different helper. It may be a code smell to me, but I'm adding tests for legacy code and am not at a point where I can refactor. How can I test this Rails helper?
module FancyHelper
def fancy_method
html = "hello"
html << another_fancy_method
html
end
end
module OtherHelper
def another_fancy_method
"world"
end
end
require "spec_helper"
describe FancyHelper do
describe "#fancy_method" do
it "does what it's supposed to" do
helper.fancy_method.should match("hello")
# NoMethodError: undefined method `another_fancy_method'
end
end
end
This is what stubs are for. When testing your helper that depends on the other helper, you will want to stub the other helper to get a predictable value and complete the test.
EDIT: https://www.relishapp.com/rspec/rspec-mocks/docs/method-stubs thanks grantovich for the newer link.

how to write/run specs for files other than model/view/controller

Using rails and rspec it's easy to have rspec generate the necessary files for me when I'm using the rails generate command with models/views/controllers. But now I want to write specs for a module I wrote. The module is in /lib/my_module.rb so I created a spec in /spec/lib/my_module_spec.rb
The problem I'm having is that when I try to do rspec spec/ the file my_module_spec.rb is run but the reference to my module in lib/my_module.rb can't be found. What's the right way to do this?
Just FYI the my_module_spec.rb file does have require 'spec_helper' in it already
require 'spec_helper'
describe "my_module" do
it "first test"
result = MyModule.some_method # fails here because it can't find MyModule
end
end
You could try including the module and maybe wrapping it in an object
require 'spec_helper'
#EDIT according to
# http://stackoverflow.com/users/483040/jaydel
require "#{Rails.root}/lib/my_module.rb"
describe MyModule do
let(:wrapper){
class MyModuleWrapper
include MyModule
end
MyModuleWrapper.new
}
it "#some_method" do
wrapper.some_method.should == "something"
end
end
Does your module contain class methods or instance methods? Remember that only class methods will be available via
MyModule.some_method
meaning that some_method is defined as
def self.some_method
...
end
If your module contains instance methods, then use Jasper's solution above. Hope that clarifies.
Put the following in your config/application.rb file:
config.autoload_paths += %W(#{Rails.root}/lib)
I was just wrestling with the same problem, and the above worked for me. There's not really any reason you should have to jump through hoops to be able to access your lib/ files from RSpec and write tests for them.

Resources