jimweirich/rspec-given

Feature request: run invariants even when no Then block given

Closed this issue · 4 comments

Hi Jim,

Saw your scotruby talk, and thought I'd given rspec-given a try, and I'm loving it!

I would like to be able to specify some invariants, and then specify some nested setup contexts, and watch it go. E.g.

describe "one element enumerables" do
  use_natural_assertions

  Invariant { subject.length == 1 }

  context do
    Given(:subject) { {a: 1} }
  end

  context do
    Given(:subject) { [:a] }
  end
end

Running this spec shows 0 examples.

Adding Then { true } at the bottom of each context has the desired effect.

Would you be open to adding an implicit Then to empty contexts which have Invariants defined, so as to trigger them? And if so, do you think detecting this (no thens but some invariants) is possible in rspec? If so, I'll start on a patch.

(I have achieved the effect I want by using shared examples, but I like the idea of being able to set up invariants about a case, then specify a number of ways of getting to that case)

Cheers,
Ian White

Thinking about this a bit more, I can see we need to distinguish the outer block in my example, from the two inner blocks. We don't want to run the invariant in the outer block, it's a setup block. The innermost blocks are leafs, and this could be the criteria for triggering invariants.

An Invariant is run after every When, or on innermost contexts with no When

Since Whens are often hoisted out into setup blocks, running invariants after a when is probably not a good idea either.

However, I can see running invariants on the inner-most blocks, even if there is no then.

The problem is that I'm not sure RSpec gives sufficient hooks in order to detect that condition (we would need some kind of end-of-context hook). If you want to investigate, please do. I would be happy to hear your results.

Thanks for your reply. I mis-commented above, I meant to say:

An Invariant is run after every Then, or on innermost contexts with no Then

I shall investigate whether rspec provides enough infrastructure for this.

As you say, there is no public api provided by rspec to detect this condition.

The best I can come up with is that specifying an invariant creates an example (it) that conditionally runs depending on whether there is a nested child group (done in a very hackish and highly coupled to rspec internals way: testing if self.class::Nested_1 is defined).

This is too hacky, so I'm closing this ticket.

However, a much simpler approach is to create a different context. For example, I load this file from my spec helper:

require 'rspec/given'
RSpec::Given.use_natural_assertions

module RunInvariants
  def run_invariants(*args, &block)
    context(*args) do
      block && block.call
      Then { true }
    end
  end
end

RSpec.configure do |config|
  config.extend(RunInvariants)
end

Which can be used like:

describe "one element things" do
  Given(:subject) { [:a] }
  Invariant { subject.length == 1 }

  run_invariants "1 elem Array"

  run_invariants "1 elem Hash" do
    Given(:subject) { {a: 1} }
  end
end

If you are interested in adding this to rspec-given, I can work on a patch. I'm not massively happy with run_invariants as a name, but can't think of anything better right now.