RSpec: how can I mixin a module into RSpec test? - ruby-on-rails

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

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

Aliasing ExampleGroup

In RSpec I can give alias to examples. For instance, alias_example_to.
Is there any way of aliasing Example Groups? I can use only describe and context. But I want to use, say, feature, scenario...etc. For example,
describe MyObject do
scenario "doing smth with object" do
...
end
end
I found an article on http://benediktdeicke.com/2013/01/custom-rspec-example-groups/.
Is there any other way to alias Example Groups.
As I interpret github, this feature was requested via https://github.com/rspec/rspec-core/issues/493 and is awaiting integration via https://github.com/rspec/rspec-core/pull/870. It is not yet available.
A possible workaround until the feature is released is this:
# spec/support/example_group_aliases.rb
module ExampleGroupAliases
extend ActiveSupport::Concern
included do
class << self
alias_method :simple, :context
end
end
module ClassMethods
def fancy(description, options = {}, &block)
context(description, options.merge(:fancy => true), &block)
end
end
RSpec.configure do |config|
config.include self
end
end
The code shows two ways of defining aliases for the context method. The first (simple) one is using alias_method. The second one (fancy) is defining a new method that then calls the original context method. The last approach allows you to do additional stuff, like adding some more options.

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

Adding Controller Macros in Rspec

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)

Resources