Testing Ruby Modules with rspec - ruby

I'm having a bad time finding on SO/Google this particular case. I have a module with functions and in order to use them, you have to create a Class which includes/extends the Module depending if you'll want instance methods or class methods.
module A
def say_hello name
"hello #{name}"
end
def say_bye
"bye"
end
end
How can I test this module using rspec?
I have something like this, and I'm not sure where is the point I should create the class and extend Module.
describe A do
class MyClass
extend A
end
before(:each) { #name = "Radu" }
describe "#say_hello" do
it "should greet a name" do
expect(Myclass.say_hello(#name)).to eq "hello Radu"
end
end
end
Thank you!

You can create an anonymous class in your tests:
describe A do
let(:extended_class) { Class.new { extend A } }
let(:including_class) { Class.new { include A } }
it "works" do
# do stuff with extended_class.say_hello
# do stuff with including_class.new.say_hello
end
end
To see something similar in real code, I've used this strategy for testing my attr_extras lib.
That said, include and extend are standard features of Ruby, so I wouldn't test that every module works both when including and when extending – that's usually a given.
If you create a named class in the test, like you do in your question, I believe that class will exist globally for the duration of your test run. So this class will leak between every test of your test suite, potentially causing conflicts somewhere.
If you use let to create an anonymous class, it will only be available inside this particular test. There is no global constant pointing to it that could conflict with other tests.
You could also use RSpec's stub_const to get a constant that doesn't leak, if you need to:
stub_const("MyClass", Class.new { … })
# do stuff with MyClass
Note that you'd run stub_const inside a before or it. Not just at the top of the file or in the class context.

To help out future readers, here's an example I got going using #henrik-n 's solution:
# slim_helpers.rb
module SlimHelpers
# resourceToTitle converts strings like 'AWS::AutoScaling::AutoScalingGroup'
# to 'Auto Scaling Group'
def resourceToTitle(input)
input.split('::')[-1].gsub(/([A-Z])/, ' \1').lstrip
end
end
# slim_helpers_spec.rb
require_relative '../slim_helpers'
describe SlimHelpers do
# extended class
let(:ec) { Class.new { extend SlimHelpers } }
it "converts AWS resource strings to titles" do
out = ec.resourceToTitle('AWS::AutoScaling::AutoScalingGroup')
expect(out).to eq 'Auto Scaling Group'
end
end

Related

Testing ruby superclass inherited functionality with rspec

Given that I have an abstract class which provides inherited functionality to subclasses:
class Superclass
class_attribute :_configuration_parameter
def self.configuration_parameter config
self._configuration_parameter = config
end
def results
unless #queried
execute
#queried = true
end
#results
end
private
# Execute uses the class instance config
def execute
#rows = DataSource.fetch self.class._configuration_parameter
#results = Results.new #rows, count
post_process
end
def post_process
#results.each do |row|
# mutate results
end
end
end
Which might be used by a subclass like this:
class Subclass < Superclass
configuration_parameter :foo
def subclass_method
end
end
I'm having a hard time writing RSpec to test the inherited and configured functionality without abusing the global namespace:
RSpec.describe Superclass do
let(:config_parameter) { :bar }
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
let(:test_instance) do
test_subclass.new
end
describe 'config parameter' do
it 'sets the class attribute' do
expect(test_subclass._configuration_parameter).to be(config_parameter)
end
end
describe 'execute' do
it 'fetches the data from the right place' do
expect(DataSource).to receive(:fetch).with(config_parameter)
instance.results
end
end
end
The real world superclass I'm mocking here has a few more configuration parameters and several other pieces of functionality which test reasonably well with this pattern.
Am I missing something obviously bad about the class or test design?
Thanks
I'm just going to jump to the most concrete part of your question, about how to avoid using a global variable to pass a local parameter to the dummy class instantiated in your spec.
Here's your spec code:
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
If you take the value returned from Class.new you can call configuration_parameter on that with the local value and avoid the global. Using tap does this with only a minor change to your existing code:
let(:test_subclass) do
Class.new(SuperClass).tap do |klass|
klass.configuration_parameter config_parameter
end
end
As to the more general question of how to test functionality inherited from a superclass, I think the general approach of creating a stub subclass and writing specs for that subclass is fine. I personally would make your _configuration_parameter class attribute private, and rather than testing that the configuration_parameter method actually sets the value, I'd instead focus on checking that the value is different from the superclass value. But I'm not sure that's in the scope of this question.

How to avoid using allow_any_instance_of?

Imagine we have following piece of code:
class A
def create_server
options = {
name: NameBuilder.new.build_name
}
do_some_operations(options)
end
end
To test such methods, I've used to use allow_any_instance_of:
it 'does operations' do
allow_any_instance_of(NameBuilder).to receive(:build_name)
# test body
end
But docs advise us not to use it because of several reasons. How then avoid allow_any_instance_of? I've came to only one solution:
class A
def create_server
options = {
name: builder.build_name
}
do_some_operations
end
private
def builder
NameBuilder.new
end
end
But with such approach code quickly becomes full of almost useless methods (especially when you actively using composition of different objects in described class).
In the absence of dependency injection as per Uzbekjon's answer (which I agree with) you could also consider stubbing out the call to NameBuilder.new so you can have direct control of the instance of NameBuilder under test:
class NameBuilder
def build_name
# builds name...
end
end
class A
def create_server
options = {
name: NameBuilder.new.build_name
}
do_some_operations(options)
end
def do_some_operations(options)
# does some operations
end
end
RSpec.describe A do
let(:a) { described_class.new }
describe '#create_server' do
let(:name_builder) { instance_double(NameBuilder) }
before do
allow(NameBuilder).to receive(:new).and_return(name_builder)
end
it 'does operations' do
# the first expectation isn't really part of what you seem
# to want to test, but it shows that this way of testing can work
expect(name_builder).to receive(:build_name)
expect(a).to receive(:do_some_operations)
a.create_server
end
end
end
If it is difficult to test, it means you have a problem in your class design. In your case, when you are doing testing for specific method call on a specific class within a class you are testing like this:
allow_any_instance_of(NameBuilder).to receive(:build_name)
Your test know exactly how the method is implemented internally. Your classes should encapsulate the logic and hide it. You are doing exactly the opposite.
You should not be testing any internal method logic. Just test the behaviour. Give inputs and test the correctness of the output.
If you really want to test that method call on NameBuilder class, then inject that dependency and make your class more testable. This also follows OOP principles.
class A
def create_server(builder)
do_some_operations(name: builder.build_name)
end
end

Specs not running as "sandbox" creating dynamic Classes

I have a module with a define method that creates a class dynamically like this:
require "active_support/all"
class SomeBaseClass
# code
end
module MyModule
def self.define(_class_name)
class_name = _class_name.classify
Object.const_set(class_name, Class.new(SomeBaseClass))
end
end
Then, if I do: MyModule.define(:my_class) I will get: MyClass
My spec:
describe "#define" do
it "creates a dynamic class" do
MyModule.define("my_class")
expect(subject.const_defined?("MyClass")).to be_truthy
end
end
This works beautifully... But! when I create a new spec defining MyClass I get this warning: warning: already initialized constant MyClass
This is happening because I've been created MyClass in the previous spec. So, the question is: How can I avoid this? I want "a fresh start" on each spec.
UPDATE: solution based on #giglemad answer...
before do
Object.send(:remove_const, :MyClass) if Object.const_defined?("MyClass")
end
describe "#define" do
it "creates a dynamic class" do
MyModule.define("my_class")
expect(subject.const_defined?("MyClass")).to be_truthy
end
end
You could define a different class name every time based on the name + some convention. I use timestamps to do so.
def nano_timestamp_string
Time.now.to_f.to_s.sub('.','')
end
You can pretty much reuse that convention any time you need something unique but still want to write your tests the same way. I use it for unique emails for instance. If you still want to def and then undef a constant with the same name then you might want to use
Object.send(:remove_const,:Myconst)

Better way to turn a ruby class into a module than using refinements?

Module#refine method takes a class and a block and returns a refinement module, so I thought I could define:
class Class
def include_refined(klass)
_refinement = Module.new do
include refine(klass) {
yield if block_given?
}
end
self.send :include, _refinement
end
end
and the following test passes
class Base
def foo
"foo"
end
end
class Receiver
include_refined(Base) {
def foo
"refined " + super
end
}
end
describe Receiver do
it { should respond_to(:foo) }
its(:foo) { should eq("refined foo") }
end
So, using refinements, I can turn a class into a module, refine its behaviour on the fly, and include it in other classes.
Is there a simpler way to turn a class into a module in Ruby (say in ruby < 2)?
In the C-implementation of rb_mod_refine
we see
refinement = rb_module_new();
RCLASS_SET_SUPER(refinement, klass);
Is this just setting the superclass of refinement to klass that copies the implementation of the class inside the refinement module?
I am aware that multiple inheritance IS
done via Modules, but what would the community think of the above Class#include_refined?
Would it be reasonable to extract this aspect out of refinements?
"Locally" patching inside a Class instead of using "using" switches to activate refinements?
I am happy indeed with Ruby 2.1 (and later) class-level "private" scope of refinements. My example above can be rephrased as:
# spec/modulify_spec.rb
module Modulify
refine(Class) do
def include_refined(klass)
_refined = Module.new do
include refine(klass) { yield if block_given? }
end
include _refined
end
end
end
class A
def a
"I am an 'a'"
end
end
class B
using Modulify
include_refined(A) do
def a
super + " and not a 'b'"
end
end
def b
"I cannot say: " + a
end
end
RSpec.describe B do
it "can use refined methods from A" do
expect(subject.b).to eq "I cannot say: I am an 'a' and not a 'b'"
end
end
and suits as solution for the original problem.
Andrea, thank you for the info in comment. Excuse my lack of knowledge to understand this is really necessary though it sounds doable as per your research.
I don't think we need to go so low level to do something in Rails.
If I'm going to do similar on Engine, I will try the following ideas, from easy to hard.
In routes.rb, mount the whole engine in right route.
I'm afraid this most common usage can't fit your need
In routes.rb, Customize engine's route for specific controllers in application route.
Devise, as an engine, can do easily. But I know not every engine could do this.
In routes.rb, redirect specific or whole set of routes to engine's routes
In your application's action, redirect to specific engine's action in application's action.
This should be customized enough for specific action
class FoosController < ApplicationController
def foo
redirect_to some_engine_path if params[:foo] == 'bar'
end
Inherit the engine's controller - for a set of actions, and if all above can't fit
*The engine's classes are available in all application, you can inherit a controller from them, instead of normal ApplicationController.
# class FoosController < ApplicationController
class FoosController < BarEngine::BarsController
*Since most engine's controller inherit from ApplicationController, this inheritance still allows you to use your own things from ApplicationController, no bad effect at all.
If all above can't do, I can try to serve a customized locally or from my github repo.
In conclusion, the above should be able to solve most of cases, and I myself prefer #5 when possible and needed.

Testing modules in RSpec

What are the best practices on testing modules in RSpec? I have some modules that get included in few models and for now I simply have duplicate tests for each model (with few differences). Is there a way to DRY it up?
The rad way =>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
Alternatively you can extend the test class with your module:
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Using 'let' is better than using an instance variable to define the dummy class in the before(:each)
When to use RSpec let()?
What mike said. Here's a trivial example:
module code...
module Say
def hello
"hello"
end
end
spec fragment...
class DummyClass
end
before(:each) do
#dummy_class = DummyClass.new
#dummy_class.extend(Say)
end
it "get hello string" do
expect(#dummy_class.hello).to eq "hello"
end
For modules that can be tested in isolation or by mocking the class, I like something along the lines of:
module:
module MyModule
def hallo
"hallo"
end
end
spec:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
It might seem wrong to hijack nested example groups, but I like the terseness. Any thoughts?
I found a better solution in rspec homepage. Apparently it supports shared example groups. From https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples!
Shared Example Groups
You can create shared example groups
and include those groups into other
groups.
Suppose you have some behavior that
applies to all editions of your
product, both large and small.
First, factor out the “shared”
behavior:
shared_examples_for "all editions" do
it "should behave like all editions" do
end
end
then when you need define the behavior
for the Large and Small editions,
reference the shared behavior using
the it_should_behave_like() method.
describe "SmallEdition" do
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end
Off the top of my head, could you create a dummy class in your test script and include the module into that? Then test that the dummy class has the behaviour in the way you'd expect.
EDIT: If, as pointed out in the comments, the module expects some behaviours to be present in the class into which it's mixed, then I'd try to implement dummies of those behaviours. Just enough to make the module happy to perform its duties.
That said, I'd be a little nervous about my design when a module expects a whole lot from its host (do we say "host"?) class - If I don't already inherit from a base class or can't inject the new functionality into the inheritance tree then I think I'd be trying to minimise any such expectations that a module might have. My concern being that my design would start to develop some areas of unpleasant inflexibility.
The accepted answer is the right answer I think, however I wanted to add an example how to use rpsecs shared_examples_for and it_behaves_like methods. I mention few tricks in the code snippet but for more info see this relishapp-rspec-guide.
With this you can test your module in any of the classes which include it. So you really are testing what you use in your application.
Let's see an example:
# Lets assume a Movable module
module Movable
def self.movable_class?
true
end
def has_feets?
true
end
end
# Include Movable into Person and Animal
class Person < ActiveRecord::Base
include Movable
end
class Animal < ActiveRecord::Base
include Movable
end
Now lets create spec for our module: movable_spec.rb
shared_examples_for Movable do
context 'with an instance' do
before(:each) do
# described_class points on the class, if you need an instance of it:
#obj = described_class.new
# or you can use a parameter see below Animal test
#obj = obj if obj.present?
end
it 'should have feets' do
#obj.has_feets?.should be_true
end
end
context 'class methods' do
it 'should be a movable class' do
described_class.movable_class?.should be_true
end
end
end
# Now list every model in your app to test them properly
describe Person do
it_behaves_like Movable
end
describe Animal do
it_behaves_like Movable do
let(:obj) { Animal.new({ :name => 'capybara' }) }
end
end
To test your module, use:
describe MyCoolModule do
subject(:my_instance) { Class.new.extend(described_class) }
# examples
end
To DRY up some things you use across multiple specs, you can use a shared context:
RSpec.shared_context 'some shared context' do
let(:reused_thing) { create :the_thing }
let(:reused_other_thing) { create :the_thing }
shared_examples_for 'the stuff' do
it { ... }
it { ... }
end
end
require 'some_shared_context'
describe MyCoolClass do
include_context 'some shared context'
it_behaves_like 'the stuff'
it_behaves_like 'the stuff' do
let(:reused_thing) { create :overrides_the_thing_in_shared_context }
end
end
Resources:
Shared Examples
Shared Context
BetterSpecs
my recent work, using as little hard-wiring as possible
require 'spec_helper'
describe Module::UnderTest do
subject {Object.new.extend(described_class)}
context '.module_method' do
it {is_expected.to respond_to(:module_method)}
# etc etc
end
end
I wish
subject {Class.new{include described_class}.new}
worked, but it doesn't (as at Ruby MRI 2.2.3 and RSpec::Core 3.3.0)
Failure/Error: subject {Class.new{include described_class}.new}
NameError:
undefined local variable or method `described_class' for #<Class:0x000000063a6708>
Obviously described_class isn't visible in that scope.
What about:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
I would suggest that for larger and much used modules one should opt for the "Shared Example Groups" as suggested by #Andrius here. For simple stuff for which you don't want to go through the trouble of having multiple files etc. here's how to ensure maximum control over the visibility of your dummy stuff (tested with rspec 2.14.6, just copy and paste the code into a spec file and run it):
module YourCoolModule
def your_cool_module_method
end
end
describe YourCoolModule do
context "cntxt1" do
let(:dummy_class) do
Class.new do
include YourCoolModule
#Say, how your module works might depend on the return value of to_s for
#the extending instances and you want to test this. You could of course
#just mock/stub, but since you so conveniently have the class def here
#you might be tempted to use it?
def to_s
"dummy"
end
#In case your module would happen to depend on the class having a name
#you can simulate that behaviour easily.
def self.name
"DummyClass"
end
end
end
context "instances" do
subject { dummy_class.new }
it { subject.should be_an_instance_of(dummy_class) }
it { should respond_to(:your_cool_module_method)}
it { should be_a(YourCoolModule) }
its (:to_s) { should eq("dummy") }
end
context "classes" do
subject { dummy_class }
it { should be_an_instance_of(Class) }
it { defined?(DummyClass).should be_nil }
its (:name) { should eq("DummyClass") }
end
end
context "cntxt2" do
it "should not be possible to access let methods from anohter context" do
defined?(dummy_class).should be_nil
end
end
it "should not be possible to access let methods from a child context" do
defined?(dummy_class).should be_nil
end
end
#You could also try to benefit from implicit subject using the descbie
#method in conjunction with local variables. You may want to scope your local
#variables. You can't use context here, because that can only be done inside
#a describe block, however you can use Porc.new and call it immediately or a
#describe blocks inside a describe block.
#Proc.new do
describe "YourCoolModule" do #But you mustn't refer to the module by the
#constant itself, because if you do, it seems you can't reset what your
#describing in inner scopes, so don't forget the quotes.
dummy_class = Class.new { include YourCoolModule }
#Now we can benefit from the implicit subject (being an instance of the
#class whenever we are describing a class) and just..
describe dummy_class do
it { should respond_to(:your_cool_module_method) }
it { should_not be_an_instance_of(Class) }
it { should be_an_instance_of(dummy_class) }
it { should be_a(YourCoolModule) }
end
describe Object do
it { should_not respond_to(:your_cool_module_method) }
it { should_not be_an_instance_of(Class) }
it { should_not be_an_instance_of(dummy_class) }
it { should be_an_instance_of(Object) }
it { should_not be_a(YourCoolModule) }
end
#end.call
end
#In this simple case there's necessarily no need for a variable at all..
describe Class.new { include YourCoolModule } do
it { should respond_to(:your_cool_module_method) }
it { should_not be_a(Class) }
it { should be_a(YourCoolModule) }
end
describe "dummy_class not defined" do
it { defined?(dummy_class).should be_nil }
end
You can also use the helper type
# api_helper.rb
module Api
def my_meth
10
end
end
# spec/api_spec.rb
require "api_helper"
RSpec.describe Api, :type => :helper do
describe "#my_meth" do
it { expect( helper.my_meth ).to eq 10 }
end
end
Here's the documentation: https://www.relishapp.com/rspec/rspec-rails/v/3-3/docs/helper-specs/helper-spec
you need to simply include your module to your spec file
mudule Test
module MyModule
def test
'test'
end
end
end
in your spec file
RSpec.describe Test::MyModule do
include Test::MyModule #you can call directly the method *test*
it 'returns test' do
expect(test).to eql('test')
end
end
One possible solution for testing module method which are independent on class that will include them
module moduleToTest
def method_to_test
'value'
end
end
And spec for it
describe moduleToTest do
let(:dummy_class) { Class.new { include moduleToTest } }
let(:subject) { dummy_class.new }
describe '#method_to_test' do
it 'returns value' do
expect(subject.method_to_test).to eq('value')
end
end
end
And if you want to DRY test them, then shared_examples is good approach
This is a recurrent pattern since you're going to need to test more than one module. For that reason, this is more than desirable to create a helper for this.
I found this post that explains how to do it but I'm coping here since the site might be taken down at some point.
This is to avoid the object instances do not implement the instance method: :whatever error you get when trying to allow methods on dummy class.
Code:
In spec/support/helpers/dummy_class_helpers.rb
module DummyClassHelpers
def dummy_class(name, &block)
let(name.to_s.underscore) do
klass = Class.new(&block)
self.class.const_set name.to_s.classify, klass
end
end
end
In spec/spec_helper.rb
# skip this if you want to manually require
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
RSpec.configure do |config|
config.extend DummyClassHelpers
end
In your specs:
require 'spec_helper'
RSpec.shared_examples "JsonSerializerConcern" do
dummy_class(:dummy)
dummy_class(:dummy_serializer) do
def self.represent(object)
end
end
describe "#serialize_collection" do
it "wraps a record in a serializer" do
expect(dummy_serializer).to receive(:represent).with(an_instance_of(dummy)).exactly(3).times
subject.serialize_collection [dummy.new, dummy.new, dummy.new]
end
end
end

Resources