Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create RSpec context inside a function

In order to avoid repeating myself a lot in my Rspec tests, I'd like to write a function like this

def with_each_partner(&block)
  PARTNER_LIST.each do |partner|
    context "with partner #{partner.name}" { yield partner }
  end
end

I have such a function and it works in the sense that all the tests run with the correct value for partner supplied, but they don't print during output as being part of the "with partner X" context: instead if I have a test like this:

describe Thing do
  subject { Thing.new(partner) }
  with_each_partner do |partner|
    it 'does its thing' do
      expect(subject.do).to eq 'its thing'
    end
  end
end

I end up with output like this:

Thing
  does its thing

Instead of the desired output, which is like:

Thing
  with partner X
    does its thing
  with partner Y
    does its thing

How can I get RSpec to correctly work with the context created in my function?

like image 732
G Gordon Worley III Avatar asked Oct 23 '14 23:10

G Gordon Worley III


1 Answers

TL;DR: do this:

def with_each_partner(&block)
  PARTNER_LIST.each do |partner|
    context "with partner #{partner.name}" do
      class_exec(&block)
    end
  end
end

Explanation

RSpec's DSL works by evaluating the blocks with a changed self -- this is how it is a method within a describe or context block, but not outside of it. When you yield, the provided block is evaluated with the original self that was self at the point the block was defined. What that means is that with your original with_each_partner definition, this code:

describe Thing do
  subject { Thing.new(partner) }
  with_each_partner do |partner|
    it 'does its thing' do
      expect(subject.do).to eq 'its thing'
    end
  end
end

Is really being evaluated like this:

describe Thing do
  subject { Thing.new(partner) }
  outer_example_group = self
  with_each_partner do |partner|
    outer_example_group.it 'does its thing' do
      expect(subject.do).to eq 'its thing'
    end
  end
end

...and so the individual examples are defined on the outer example group, not on the "with partner #{partner.name}" nested group.

class_exec evaluates the provided block in the context of the class/module. In this case, the class is the example group subclass that RSpec has generated for your context. Using class_exec ensures that when it is called, the receiver is your nested context example group rather than the outer example group, creating the result you want.

like image 142
Myron Marston Avatar answered Sep 28 '22 11:09

Myron Marston