Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using described_class when including a module under test

Tags:

ruby

rspec

I am writing some rspec's to test some modules correctly implement their behaviour.

The module looks like this:

module Under::Test
  def some_behaviour
  end
end

I can't do this in RSpec:

describe Under::Test do
  subject {Class.new{include described_class}.new}

At the point #described_class is called, it can no longer be resolved because self is an instance of Class, which doesn't have a #describe_class method. So I am forced to repeat myself:

subject {Class.new{include Under::Test}.new}

Or use it in the spec in a different way to how it's used by my clients:

subject {Object.new.extend described_class}

This has the same end effect, but something in me thinks that if I'm asking my clients to include Under::Test, then the tests should look as close to how it's used by them as possible.

I can use the closure property to fix this, but I wonder if it's no better. Does this have code smell?

describe Under::Test do
  subject {mudule = described_class;Class.new{include mudule}.new}

  it 'has some behaviour' do
    expect(subject.some_behaviour).to be
  end
end

Note, I also asked in r/ruby on reddit, someone there suggested:

subject {Class.new.include(described_class).new}

which might be how I go.

like image 954
Leif Avatar asked Nov 09 '15 03:11

Leif


2 Answers

If your eventual aim is to include the current described_class module in a newly created Class, how about the following workaround?

RSpec.describe NewModule do
  let(:test_class) do
    Class.new.tap do |klass|
      klass.include(described_class)
    end
  end

  specify do
    expect(test_class.ancestors).to include described_class # passes
  end
end

And here's an example of including the module's methods in an object:

module NewModule
  def identity
    itself
  end
end

RSpec.describe NewModule do
  let(:one) do
    1.tap do |obj|
      obj.class.include(described_class)
    end
  end

  specify do
    expect(one.identity).to eq 1 # passes
  end
end

Note that the include method for a Class isn't private in Ruby v2+. If you're using an older version, you have to use klass_name.send(:include, described_class)

like image 57
SHS Avatar answered Nov 07 '22 00:11

SHS


I think both of your solutions are just fine, I like a little bit more

Object.new.extend described_class

for its readability and conciseness. If you want to use Class.new approach I think it's ok to repeat module name, as Jared said, readability is more important here than dryness.

like image 3
Alexey Shein Avatar answered Nov 07 '22 01:11

Alexey Shein