How to Mix a Module into an Rspec Context

How to mix a module into an rspec context

You can use RSpec's shared_context:

shared_context 'constants' do
FOO = 1
end

describe Model do
include_context 'constants'

p FOO # => 1
end

Using a Helpers module to declare contexts in Rspec

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.

How to test module in a module with rspec?

First of all, module A do syntax seems to be wrong - there shouldn't be a do word. The second thing is that you tries to call foo method on a module A::B (there is no class method defined on A::B - foo is an instance method). Your code should looks like:

module A
module B
def self.foo
end
end
end

*(In this case, you can call A::B.foo)

OR if you want to have foo as an instance method:

module A
module B
def foo
end
end
end

In this case you cannot call A::B.foo, but you can create a class, which can include A::B module:

class Test
include A::B
end

Now you can call Test.new.foo

Regarding rspec testing:

  • class method: as you described
  • instance method:

    • define Test class in your spec and then test this class
    • include this module to your rspec context (ugly way)

How can I change the module context of an RSpec describe block?

You can use the described_class helper...

describe Foo::Bar::Baz::Quux::Widget do
it "has described_class helper" do
described_class.should == Foo::Bar::Baz::Quux::Widget
end
end

Or, for the lol:

describe Foo::Bar::Baz::Quux::Widget do
def Widget
described_class
end

it "has described_class helper" do
Widget.should == Foo::Bar::Baz::Quux::Widget
end
end

How do I call a module method in rspec, with the current module set up

You can't call instance methods of a module. You must include or extend the module and then call the methods on the object which included or extended the module. The error you are getting from rspec is because you're calling fetch_tweets from within your example, but fetch_tweets is not defined within the example.

Once you solve the problem of invoking fetch_tweets, then you've got to deal with the fact that your expect(fetch_tweets(...)) expectation is saying that the object returned by fetch_tweets is going to receive a method call of :save_tweets, which is not what you want. Per your description, you want to set an expectation that whatever object included or extended TwitterHandler will receive save_tweets and then you want to call fetch_tweets.

If this isn't clear, I suggest you read up on Ruby module methods and then read up on RSpec.

How to permit unqualified access to constants of a module in Rspec tests?

One approach is to define your RSpec tests in the context of the module:

module MyConsts
FOO = "foo"
end

module MyConsts
RSpec.describe "Tests Module Constants" do
it "has unqualified access to constants." do
expect(FOO).to eq("foo")
end
end
end

RSpec.describe defines a module, and if it's defined as a child of MyConsts, then constant lookups will bubble up to MyConsts.

You could also just mix MyConsts in at the top level:

include MyConsts

This approach may be less desirable, as it would make those consts usable from anywhere, making them effectively global, which may be leakier than you'd like.

Testing modules in RSpec

I found a better solution in rspec homepage. Apparently it supports shared example groups. From https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples!

Shared Example Groups

You can create shared example groups
and include those groups into other
groups.

Suppose you have some behavior that
applies to all editions of your
product, both large and small.

First, factor out the “shared”
behavior:

shared_examples_for "all editions" do   
it "should behave like all editions" do
end
end

then when you need define the behavior
for the Large and Small editions,
reference the shared behavior using
the it_should_behave_like() method.

describe "SmallEdition" do  
it_should_behave_like "all editions"
it "should also behave like a small edition" do
end
end

Create RSpec context inside a function

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.

Automatically share context in RSpec

You can set up global before hooks using RSpec.configure via configure-class-methods and Configuration:

RSpec.configure {|c| c.before(:all) { do_stuff }}

let is not supported in RSpec.configure, but you can set up a global let by including it in a SharedContext module and including that module using config.before:

module MyLetDeclarations
extend RSpec::Core::SharedContext
let(:foo) { Foo.new }
end
RSpec.configure { |c| c.include MyLetDeclarations }


Related Topics



Leave a reply



Submit