RSpec With Namespaced Dummy Classes + Eigenclass Fun

29 Mar 2016, by Sia Sajjadi

Generally, you don't need to use dummy classes with rspec.

To clarify

Use doubles if you can!

The rspec mocks and stubs work well and they offer some protection against mocks/stubs becoming out of sync with the objects that they are imitating.

But some times you need a dummy class (or feel like you do anyway)

How to RSpec & Namespaced Dummy classes

First things first, I don't want

class Dummy
end

nor do I want

class SomeAnnoying::Namespace::ForAnExample::Dummy
end

or

class SomeAnnoying::Namespace::ForAnotherExample::Dummy
end

Option 1 sucks.
Option 2 is better, if done once only
Option 3 is the same as 2, for n example groups you may need up to n namespaces that need naming, consideration, etc.

This is annoying_

A yummy way

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

And finally in your specs you can do

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

Now you have:

> dummy
=> RSpec::ExampleGroups::ApplicationController::BehavesLikeJsonSerializerConcern::SerializeCollection::Dummy
> dummy_serializer
=> RSpec::ExampleGroups::ApplicationController::BehavesLikeJsonSerializerConcern::SerializeCollection::DummySerializer

Nice automatic, no?

Eigenclass fun

As an aside, sometimes you can do crazy stuff like:

  dummy_class :dummy do
    def errors
      @errors ||= Object.new.tap do |o|
        def o.full_messages
            []
        end
      end
    end
  end

Explanation

eigenclasses are also known as singletons

The instances of the dummy class above have a method errors such that instance_of_dummy.errors returns an instance of Object. This instance of object has the method full_errors defined on it, such that

> instance_of_object = instance_of_dummy.errors
> instance_of_object.full_messages
=> []

But if we do

> Object.new.full_messages
=>  NoMethodError: undefined method `full_messages' for #<Object:0x0055fd025fee98>

What we have is an object who's behaviour is defined by it's class as well as it's eigenclass. The full_messages method exists on the eigenclass for this instance, no others.

It's useful because every object in ruby has an eigenclass
Anything defined on or scoped to an eigenclass is only shared by inheritance, which is a non-issue for the instances of classes unless you're doing serious voodoo.

It also means that you can quickly mock up a specific behaviour without having to define classes, structs or doubles. Not that there's anything wrong with that. But this works too :)


Cookies help us deliver our services. By using our services, you agree to our use of cookies.