jimweirich / rspec-given

Given/When/Then keywords for RSpec Specifications
https://github.com/jimweirich/rspec-given
MIT License
653 stars 61 forks source link

Feature request: run invariants even when no Then block given #24

Closed ianwhite closed 11 years ago

ianwhite commented 11 years ago

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

ianwhite commented 11 years ago

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

jimweirich commented 11 years ago

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.

ianwhite commented 11 years ago

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.

ianwhite commented 11 years ago

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.