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
Related
I'm trying to mock a class, so that I can expect it is instantiated and that a certain method is then called.
I tried:
expect(MyPolicy).
to receive(:new).
and_wrap_original do |method, *args|
expect(method.call(*args)).to receive(:show?).and_call_original
end
But all I'm getting is:
undefined method `show?' for #RSpec::Mocks::VerifyingMessageExpectation:0x0055e9ffd0b530
I've tried providing a block and calling the original methods first (both :new and :show?, which I had to bind first), but the error is always the same.
I know about expect_any_instance_of, but it's considered code-smell, so I'm trying to find another way to do it properly.
Context: I have pundit policies and I want to check whether or not it has been called
I also tried, with the same error:
ctor = policy_class.method(:new)
expect(policy_class).
to receive(:new).
with(user, record) do
expect(ctor.call(user, record)).to receive(query).and_call_original
end
You broke MyPolicy.new.
Your wrapper for new does not return a new MyPolicy object. It returns the result of expect(method.call(*args)).to receive(:show?).and_call_original which is a MessageExpectation.
Instead, you can ensure the new object is returned with tap.
# This is an allow. It's not a test, it's scaffolding for the test.
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
method.call(*args).tap do |obj|
expect(obj).to receive(:show?).and_call_original
end
end
Or do it the old fashioned way.
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
obj = method.call(*args)
expect(obj).to receive(:show?).and_call_original
obj
end
It is often simpler to separate the two steps. Mock MyPolicy.new to return a particular object and then expect the call to show? on that object.
let(:policy) do
# This calls the real MyPolicy.new because policy is referenced
# when setting up the MyPolicy.new mock.
MyPolicy.new
end
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
it 'shows' do
expect(policy).to receive(:show?).and_call_original
MyPolicy.new.show?
end
This does mean MyPolicy.new always returns the same object. That's an advantage for testing, but might break something. This is more flexible since it separates the scaffolding from what's being tested. The scaffolding can be reused.
RSpec.describe SomeClass do
let(:policy) {
MyPolicy.new
}
let(:thing) {
described_class.new
}
shared_context 'mocked MyPolicy.new' do
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
end
describe '#some_method' do
include_context 'mocked new'
it 'shows a policy' do
expect(policy).to receive(:show?).and_call_original
thing.some_method
end
end
describe '#other_method' do
include_context 'mocked MyPolicy.new'
it 'checks its policy' do
expect(policy).to receive(:check)
thing.other_method
end
end
end
Finally, inaccessible constructor calls are a headache both for testing, and they're inflexible. It's a default which cannot be overridden.
class SomeClass
def some_method
MyPolicy.new.show?
end
end
Turn it into an accessor with a default.
class SomeClass
attr_writer :policy
def policy
#policy ||= MyPolicy.new
end
def some_method
policy.show?
end
end
Now it can be accessed in the test or anywhere else.
RSpec.describe SomeClass do
let(:thing) {
described_class.new
}
describe '#some_method' do
it 'shows its policy' do
expect(thing.policy).to receive(:show?).and_call_original
thing.some_method
end
end
end
This is the most robust option.
Given that:
Testing private methods is not a good practice.
An interface with many public methods is not a good practice either.
Suppose I have the following code:
module RedisHelper
private
def key_for(id)
[prefix, id].join('-')
end
def prefix
self.class.name.split('::').last.underscore
end
end
class X
include RedisHelper
def perform(id)
key = key_for(id)
redis.sadd(key, [:bar])
end
end
class Y
include RedisHelper
def perform(id)
key = key_for(id)
redis.set(key, :foo)
end
end
What would be the best way to test the behavior of key_for/prefix methods without overtesting them? I wouldn't like to repeat its logic when testing classes X and Y.
I don't think that turning RedisHelper's methods public is good, because they would be public in X and Y also.
Direct answer
Test the module directly:
def test_redis_key_namespacing
dummy_class = Class.new do # this anonymous class will not pollute namespace
include RedisHelper
public :key_for
def self.name
'Dummy'
end
end
assert_equal 'Dummy-hello', dummy_class.new.key_for('hello')
end
Overall Suggestion
I recommend not using a RedisHelper module. It's good that you're trying to keep "Redis namespacing code" logic together and separated from other code, but what you really have is "Redis namespacing code" all over the place.
key_for is in both X and Y -- they have to handle the namespacing themselves every call to redis. Also, both key_for & prefix can be called in any of those classes (and subclasses and any other included modules) even though they're both internal to "Redis namespacing".
It looks like "Redis key namespacing" is an important concept in your application. It's reused in multiple places by different unrelated areas. Make it "a thing":
class NsRedis
key_methods = Redis.instance_methods(false).select do |m|
Redis.instance_method(m).parameters.first == [:req, :key]
end
# some metaprogramming, but it's static, much better than method_missing!
key_methods.each do |m_name|
define_method(m_name) do |key, *args|
Redis.current.public_send("#{#namespace}:#{key}", *args)
end
end
def initialize(namespace)
#namespace = namespace
end
end
# Now, anywhere you're going to need it, including tests:
class X
def initialize
#redis = NsRedis.new(self.class.name)
end
def do_something
#redis.set(:my_key, 'my_val') # "unaware" of the namespacing at usage
end
end
In RSpec you can use shared examples:
RSpec.shared_examples "Redis namespacing helper" do
# your tests for key_for, prefix and what-have-you
end
RSpec.describe X do
it_behaves_like "Redis namespacing helper"
end
RSpec.describe Y do
it_behaves_like "Redis namespacing helper"
end
In minitest this is probably handled by simply including the module with shared tests.
RSpec doubles can not be changed by extending its instances.
Minimal example
Please note that the example described here is just a minimal example
to demonstrate the problem. The original classes are more complex and
the behavior that is specced (obj.extend(Something)) is needed and
can not be changed.
Spec
Let us have a look at the spec first.
In the following example you can see the spec how I'd like it to look like:
require 'spec_helper'
RSpec.describe Modifier do
subject(:modifier) { described_class.new(active) }
describe "#apply_to(obj)" do
subject(:obj) { instance_double(Foo, foo: "foo") }
before { modifier.apply_to(obj) }
context "when active" do
let(:active) { true }
its(:foo) { is_expected.to eq("extended-foo") } # NOTE: This one will fail
end
context "when not active" do
let(:active) { false }
its(:foo) { is_expected.to eq("foo") }
end
end
end
Unfortunately, this is not working :(
Failures:
1) Modifier#apply_to(obj) when active foo should eq "extended-foo"
Failure/Error: its(:foo) { is_expected.to eq("extended-foo") }
expected: "extended-foo"
got: "foo"
Foo
class Foo
attr_reader :foo
def initialize(foo)
#foo = foo
end
end
Modifier
This class will modify given objects by extending its instance methods.
class Modifier
def initialize(active)
#active = active
end
# NOTE: This is the interesting method
def apply_to(obj)
return unless active?
obj.extend(Extended) # NOTE: And this is the interesting LOC
end
def active?
#active
end
private
attr_reader :active
module Extended
def foo
"extended-#{super}"
end
end
end
Even more minimal example
The whole problem can be broken down to the following code snippet:
module Bar
def foo
"bar"
end
end
double = RSpec::Mocks::Double.new("Foo", foo: "foo")
obj = Object.new
double.extend(Bar)
obj.extend(Bar)
double.foo
# => "foo"
obj.foo
# => "bar"
Conclusion
RSpec doubles can not be changed by extending its instances, which is expected behavior?
If so, how can you create readable specs for the example described here?
Links
Repo to reproduce: Extend RSpec doubles (Example)
RSpec: Using an instance double
GitHub: RSpec Issue 1100
If the spec knows that it expexts an extendend object, why not expect the extend call itself?
expect(obj).to receive(:extend).and_return { class.new.extend(...) }
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
I have situaltion like this:
module Something
def my_method
return :some_symbol
end
end
class MyClass
include Something
def my_method
if xxx?
:other_symbol
else
super
end
end
end
Now the problem is with testing - I would like to ensure that super method got called from overriden method and stub it so that I can test other parts of method. How can I accomplish that using RSpec mocks?
Ensuring that super gets called sounds a lot like testing the implementation, not the behaviour, and mocking the subject-under-test isn't such a great idea anyway. I would suggest just explicitly specifying the different code paths
describe "#my_method" do
it "returns :other_symbol when xxx" do
...
end
it "returns :some_symbol when not xxx" do
...
end
end
If you had a lot of classes that included that module, you could use shared examples to reduce the duplication in your tests.
shared_examples_for "Something#my_method" do
it "returns :some_symbol" do
expect(subject.my_method).to eq :some_symbol
end
end
describe MyClass do
describe "#my_method" do
context "when xxx" do
subject { ... }
it "returns :other_symbol" do
expect(subject.my_method).to eq :other_symbol
end
end
context "when not xxx" do
subject { ... }
it_behaves_like "Something#my_method"
end
end
end
Update: If you really can't predict the behaviour of the mixin, you could switch out what method gets called by super by including another module that defines it.
If you have a class C that includes modules M and N that both define a method f, then in C#f, super will refer to whichever module was included last.
class C
include M
include N
def f
super # calls N.f because it was included last
end
end
If you include it in the singleton class of your subject-under-test, then it won't affect any other tests:
describe MyClass do
describe "#my_method" do
it "calls super when not xxx" do
fake_library = Module.new do
def my_method
:returned_from_super
end
end
subject.singleton_class.send :include, fake_library
expect(subject.my_method).to be :returned_from_super
end
end
end
Disclaimer: this doesn't actually test that the mixin works, just that super gets called. I still would advise actually testing the behaviour.