Reputation: 15472
I am trying to recursively declare contexts in Rspec from within a Helpers module.
(My code would be using these contexts in an unusual way, namely to recursively make assertions about keys in a nested Hash. Maybe I could solve this problem in a different way, but that's beside the point.)
A minimal complete example would be:
module Helpers
def context_bar
context "bar" do
it "baz" do
expect(true).to be true
end
end
end
end
include Helpers
describe "foo" do
Helpers.context_bar
end
If I now execute this code in Rspec it fails with:
RuntimeError:
Creating an isolated context from within a context is not allowed. Change `RSpec.context` to `context` or move this to a top-level scope.
I can then refactor it as this:
def context_bar
context "bar" do
it "baz" do
expect(true).to be true
end
end
end
describe "foo" do
context_bar
end
And that works just fine for me, although I lose the benefit of the readability that comes with having this method and similar methods inside a module name space.
Is there any way for me to make this work?
(Note that there is a superficial similarity of this question to others like this one, or in the Rspec docs here. This seems to make Helpers available inside examples, but it won't allow me to actually declare a context.)
Upvotes: 1
Views: 859
Reputation: 38418
You can add context in configuration. The rspec-graphql_response gem does a pretty nice job of it here. Though whether or not it is a good idea is another question.
Basically, it goes something like this:
require 'spec_helper'
# Put this in a helper somewhere.
RSpec.configure do |config|
def my_context_helper(&block)
block.call if block_given?
end
end
RSpec.describe 'Smoke Test' do
my_context_helper
my_context_helper do
# do something
end
end
You can pass context around with instance variables. I think you'd need to be careful of collisions and tearing down your specs correctly. Else you might get unexpected results in your tests. Shared examples or shared context are probably safer, easier and uses the RSpec DSL as it is intended.
I also did a spike here.
Upvotes: 1
Reputation: 15472
As suggested in the comments, the answer here, in general, is to use Shared Examples. Thus, I can refactor this code example as:
RSpec.shared_examples "context_bar" do
context "bar" do
it "baz" do
expect(true).to be true
end
end
end
describe "foo" do
include_examples "context_bar"
end
If, however, I declare a recursive "function" like:
RSpec.shared_examples "compare" do |ref1, ref2|
...
end
and call it using:
include_examples "compare", ref1, ref2
This fails with:
ArgumentError:
can't include shared examples recursively
See also shared examples in the docs.
It is also suggested in the comments that I could use a custom matcher. Indeed, someone did something very similar here.
Upvotes: 1