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
Rails Generate Has_Many Association
Activerecord Objects in Hashes Aren't Garbage Collected -- a Bug or a Sort of Caching Feature
Error Installing Any Ruby Version with Rvm on Osx
Rails 5.2.0 with Ruby 2.5.1 Console - 'Warning:' 'Already' Initialized Constant Fileutils::Version
What Are the Ruby's Object#Taint and Object#Trust Methods
Array Attribute for Ruby Model
Ruby: Explicit Scoping on a Class Definition
Ruby on Rails: What Reporting And/Or Charting Tools Are Available
How to Open Files Relative to Home Directory
Ruby Strftime: Month Without Leading Zero
How to Do Standard Deviation in Ruby
Respond_To' VS. 'Respond_To_Missing'
Why Is This String Key in a Hash Converted to a Symbol
Ruby 'Each_With_Object' with Index
How to Handle Exceptions with Ruby Rest-Client
How to Return a Value from a Thread in Ruby