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?
14 Answers
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)

- 7,471
- 3
- 35
- 27
-
1Nice. This helped me avoid all sorts of issues with class ivars spanning tests. Gave the classes names by assigning to constants. – captainpete Jun 25 '12 at 12:48
-
By "cooler" what @gri0n means is: that `let` is better than assigning an instance variable as the dummy class in a `before(:each)` (or better `before(:all)`). IMO, the best reason is that you'll get a `NameError`, instead of `nil` if you fat finger it. Have a look at [this SO on when to use let](http://stackoverflow.com/questions/5359558/when-to-use-rspec-let) – SooDesuNe Jul 14 '12 at 15:54
-
I like this. It works for the methods defined in the module. But, one of my modules has some methods which act on containing class attributes. I tried adding those in my dynamically defined proxy class with attr_accessor, but they don't work in rspec. Oddly, they do work in the console. – pduey Oct 08 '12 at 19:38
-
Is `Siliconseller::CodeGenerator` the module? – lulalala Jun 27 '13 at 02:45
-
I am getting `superclass must be a Class (Module given)` error. – lulalala Oct 14 '13 at 02:02
-
3@lulalala No, it's a super class: http://www.ruby-doc.org/core-2.0.0/Class.html#method-c-new To test modules do something like this: `let(:dummy_class) { Class.new { include ModuleToBeTested } }` – Timo Oct 21 '13 at 07:24
-
Can you somehow access this dummy class in other `let` statements? – Automatico Dec 11 '13 at 12:00
-
28Way rad. I usually do: `let(:class_instance) { (Class.new { include Super::Duper::Module }).new }`, that way I get the instance variable that is most often used for testing any way. – Automatico Jan 03 '14 at 12:26
-
Caveat: `let` can only be used in describe block, so if you looked at Carmen's answer and try to paste the following answer in your before each block it won't work (this wasn't clear for me) – Christos Hrousis Sep 06 '14 at 06:50
-
4using `include` does not work for me but `extend` does `let(:dummy_class) { Class.new { extend ModuleToBeTested } }` – user115014 Nov 14 '16 at 16:13
-
9Even radder: `subject(:instance) { Class.new.include(described_class).new }` – Richard-Degenne Nov 17 '17 at 10:39
-
1Just to be a fuss-budget...I used your model (Thanks!) but instead of calling it :dummy_class, I called it :objekt. since we're creating an instance, not a class. YMMW. – David Hempy Aug 27 '18 at 18:09
-
1As others have said, recommend using `subject` over `let` here because you're using a mock instance of the thing being tested. – Allison May 07 '19 at 03:01
-
What if you needed the parent class to implement methods? In my use case, I'm wanting to test the modules in isolation of the including class because I'm trying to isolate out the Faraday calls out of the tests, but another example might be two classes with different data sources. – RonLugge Oct 26 '20 at 17:44
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

- 36,864
- 16
- 117
- 117

- 3,454
- 3
- 18
- 9
-
4Any reason you didn't `include Say` inside of the DummyClass declaration instead of calling `extend`? – Grant Birchmeier Aug 31 '12 at 17:50
-
2grant-birchmeier, he's `extend`ing into the instance of the class, i.e. after `new` has been called. If you were doing this before `new` is called then you are right you would use `include` – Hedgehog Nov 26 '12 at 10:34
-
8I edited the code to be more concise. @dummy_class = Class.new { extend Say } is all you need to test a module. I suspect people will prefer that as we developers often do not like to type more than necessary. – Tim Harper Mar 01 '13 at 01:15
-
@TimHarper Tried but instance methods became class methods. Thoughts? – lulalala Jun 27 '13 at 03:08
-
@lulalala that's right, if you want to test a module of functions (that doesn't depend on some some state for the object into which they are mixed) then having them be class methods is ideal. In fact, it may be better to use `@helper` = Module.new { extend Say }, since you'll have no use for instantiating `@helper`. – Tim Harper Aug 01 '13 at 17:22
-
6Why would you define the `DummyClass` constant? Why not just `@dummy_class = Class.new`? Now your polluting your test environment with an unnecessary class definition. This DummyClass is defined for every and each one of your specs and in the next spec where you decide to use the same approach and reopen the DummyClass definition it might already contain something (though in this trivial example the definition is strictly empty, in real life use cases it's likely that something gets added at some point and then this approach becomes dangerous.) – Timo Oct 18 '13 at 14:36
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?

- 684
- 5
- 7
-
1
-
2Might mess up the rspec. I think using the `let` method described by @metakungfu is better. – Automatico Jan 03 '14 at 12:28
-
@Cort3z You definitely need to make sure that method names don't collide. I'm using this approach only when things are really simple. – Frank C. Schuetz Mar 10 '14 at 10:24
-
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
-
Updated link: https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/example-groups/shared-examples – Jared Oct 31 '12 at 00:55
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.

- 51,832
- 12
- 88
- 127
-
What if my module depends on class having certain attributes and behavior? – Andrius Oct 11 '09 at 15:05
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

- 3,710
- 2
- 29
- 45
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.

- 1,129
- 1
- 12
- 18
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:

- 1,925
- 1
- 18
- 25
What about:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end

- 9,757
- 2
- 65
- 61
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
-
For some reason only `subject { dummy_class.new }` is working. The case with `subject { dummy_class }` isn't working for me. – valk Aug 13 '14 at 16:52
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
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

- 6,118
- 13
- 23
-
I wasn't the one who downvoted you, but I suggest replacing your two LETs with `subject(:module_to_test_instance) { Class.new.include(described_class) }`. Otherwise I don't really see anything wrong with your answer. – Allison Sep 26 '19 at 23:13
you need to simply include your module to your spec file
module 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
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

- 4,231
- 2
- 37
- 47