I'm using rspec 3.0.0.beta1. I have to test a method which yields self:
class Test
def initialize
yield self if block_given?
end
end
This is a succesful test:
describe Test do
context 'giving a block with one argument' do
it 'yields self'
expect { |b| described_class.new &b }.to yield_with_args described_class
end
end
end
But it only tests the object class, without testing the identity to self.
This is the closest (failing) test I wrote:
describe Test do
context 'giving a block with one argument' do
it 'yields itself'
instance = nil
expect { |b|
instance = described_class.new &b
}.to yield_with_args instance
end
end
end
It fails indeed, since that, at the time the last instance occurrence is evaluated it is nil, so it doesn't match with the instance inside the block evaluation.
The yield matchers cannot be used directly in your situation. The simplest thing to do is a variation of your second code with a different matcher later.
describe Test do
context 'giving a block with one argument' do
it 'yields itself'
yielded_instance = nil
new_instance = described_class.new { |i| yielded_instance = i }
expect(yielded_instance).to be new_instance
end
end
end
Related
Here's what ended up working:
# lib file
module SlackWrapper
class << self
def client
#client ||= ::Slack::Web::Client.new
end
end
end
describe SlackWrapper do
# test file
before :each do
$mock_client = double("slack client").tap do |mock|
allow(mock).to receive(:channels_info) { channel_info }
end
module SlackWrapper
class << self
def client
$mock_client
end
end
end
end
describe "#should_reply?" do
describe "while channel is paused" do
it "is falsey" do
SlackWrapper.pause_channel message.channel
expect(
SlackWrapper.should_reply? message
).to be_falsey
end
end
describe "while channel is not paused" do
it "is truthy" do
expect(
SlackWrapper.should_reply? message
).to be_truthy
end
end
end
end
This definitely does not feel right. However, leaving $mock_client as a local var gives me undefined local variable when tests are run, and moving the double... code into the monkeypatch gives undefined method. And of course, monkeypatching.
What's the correct way to do this?
You could just stub the new method for a test block or entire spec file:
# test file
# you could also create a class double if you need its methods:
# https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/verifying-doubles/using-a-class-double
let(:slack_client) { double("slack client") }
before(:each) do
allow(::Slack::Web::Client).to receive(:new).and_return(slack_client)
end
# simple example:
it "checks slack client method to be a double" do
expect(SlackWrapper.client).to be(slack_client)
end
I understand that
def a(&block)
block.call(self)
end
and
def a()
yield self
end
lead to the same result, if I assume that there is such a block a {}. My question is - since I stumbled over some code like that, whether it makes any difference or if there is any advantage of having (if I do not use the variable/reference block otherwise):
def a(&block)
yield self
end
This is a concrete case where i do not understand the use of &block:
def rule(code, name, &block)
#rules = [] if #rules.nil?
#rules << Rule.new(code, name)
yield self
end
The only advantage I can think of is for introspection:
def foo; end
def bar(&blk); end
method(:foo).parameters #=> []
method(:bar).parameters #=> [[:block, :blk]]
IDEs and documentation generators could take advantage of this. However, it does not affect Ruby's argument passing. When calling a method, you can pass or omit a block, regardless of whether it is declared or invoked.
The main difference between
def pass_block
yield
end
pass_block { 'hi' } #=> 'hi'
and
def pass_proc(&blk)
blk.call
end
pass_proc { 'hi' } #=> 'hi'
is that, blk, an instance of Proc, is an object and therefore can be passed to other methods. By contrast, blocks are not objects and therefore cannot be passed around.
def pass_proc(&blk)
puts "blk.is_a?(Proc)=#{blk.is_a?(Proc)}"
receive_proc(blk)
end
def receive_proc(proc)
proc.call
end
pass_proc { 'ho' }
blk.is_a?(Proc)=true
#=> "ho"
I'm writing RSpec unit tests for a CommandLineInterface class that I've created for my Directory object. The CommandLineInterface class uses this Directory object to print out a list of people in my Directory. Directory has a #sort_by(param) method that returns an array of strings. The order of the strings depends on the param passed to the #sort_by method (e.g., sort_by("gender"). What would be the correct way to mock out this Directory behavior in my CLI specs? Would I use an instance_double? I am not sure how to do this for a method that takes parameters, like sorting by gender.
I'm only using Ruby and RSpec. No Rails, ActiveRecord, etc. being used here.
Snippets from the class and method I want to mock out:
class Directory
def initialize(params)
#
end
def sort_by(param)
case param
when "gender" then #people.sort_by(&:gender)
when "name" then #people.sort_by(&:name)
else raise ArgumentError
end
end
end
It all depends on how your objects are collaborating.
Some information is lacking in your question:
How does CommandLineInterface use Directory? Does it create an instance by itself or does it receive one as an argument?
Are you testing class methods or instance methods? (Prefer instance methods)
Here's how you could do it if you pass in the dependent object:
require 'rspec/autorun'
class A
def initialize(b)
#b = b
end
def foo(thing)
#b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:b) { double('an instance of B') }
let(:a) { A.new(b) }
it 'calls b.bar with qux' do
expect(b).to receive(:bar).with('qux')
a.foo('qux')
end
end
end
end
If the class initializes the dependant object and it isn't important to know which instance got the message you can do this:
require 'rspec/autorun'
B = Class.new
class A
def initialize
#b = B.new
end
def foo(thing)
#b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:a) { A.new }
it 'calls b.bar with qux' do
expect_any_instance_of(B).to receive(:bar).with('qux')
a.foo('qux')
end
end
end
end
If you just want to stub out the return value and not test whether the exact message was received, you can use allow:
require 'rspec/autorun'
B = Class.new
class A
def initialize
#b = B.new
end
def foo(thing)
thing + #b.bar(thing)
end
end
RSpec.describe A do
describe '#foo' do
context 'when given qux' do
let(:a) { A.new }
it 'returns qux and b.bar' do
allow_any_instance_of(B).to receive(:bar).with('qux') { 'jabber' }
expect(a.foo('qux')).to eq('quxjabber')
end
end
end
end
I'm currently working on an interface that allows me to wrap arbitrary method calls with a chain of procs. Without going into too much detail, I currently have an interface that accepts something like this:
class Spy
def initialize
#procs = []
end
def wrap(&block)
#procs << block
end
def execute
original_proc = Proc.new { call_original }
#procs.reduce(original_proc) do |memo, p|
Proc.new { p.call &memo }
end.call
end
def call_original
puts 'in the middle'
end
end
spy = Spy.new
spy.wrap do |&block|
puts 'hello'
block.call
end
spy.wrap do |&block|
block.call
puts 'goodbye'
end
spy.execute
What I'd like to do though is remove the |&block| and block.call from my API and use yield instead.
spy.wrap do
puts 'hello'
yield
end
This didn't work and raised a LocalJumpError: no block given (yield) error.
I've also tried creating methods by passing the proc the define_singleton_method in the reduce, but I haven't had any luck.
def execute
original_proc = Proc.new { call_original }
#procs.reduce(original_proc) do |memo, p|
define_singleton_method :hello, &p
Proc.new { singleton_method(:hello).call(&memo) }
end.call
end
Is there another approach I can use? Is there anyway to yield from a Proc or use the Proc to initialize something that can be yielded to?
Using yield in your wrap block does not make much sense unless you passed a block to the caller itself:
def foo
spy.wrap do
puts "executed in wrap from foo"
yield
end
end
If you call foo without a block it will raise the exception since yield can't find a block to execute. But if you pass a block to foo method then it will be invoked:
foo do
puts "foo block"
end
Will output
executed in wrap from foo
foo block
In conclusion I think you misunderstood how yield works and I don't think it is what you want to achieve here.
Consider this code:
def thing_incrementer
lambda do
self.foo +=1
save!
end
end
def increment_thing
with_lock &thing_incrementer
end
How can I write a test which tests that the thing_incrementer is passed with with_lock as a block? If I just wanted to test that it was passed as a parameter (without the leading &) I would do this:
let(:the_lambda){ lambda{} }
x.stub(:thing_incrementer){ the_lambda }
x.should_receive(:with_lock).with(the_lambda)
x.increment_thing
Passing &thing_incrementer passes a proc which gets bound as a block to with_thing. So, just test for that:
expect(subject).to receive(:with_lock).with(no_args) do |&blk|
expect(blk).to be_a(Proc)
end
If you want to pass a lambda as an argument, then you wouldn't prefix it with & and it would just get passed as a normal argument, but then you'd have to call blk.call (or whatever) rather than just yielding to the block.
To check that you're receiving the lambda you want:
class Foo
def incrementor
-> {}
end
def increment
with_lock &incrementor
end
def with_lock
yield
end
end
describe "Lock" do
subject { Foo.new }
let(:the_lambda) { -> {} }
before do
expect(subject).to receive(:incrementor).and_return(the_lambda)
end
it "should receive the_lambda from the incrementor" do
expect(subject).to receive(:with_lock).with(no_args) do |&blk|
expect(blk).to eq(the_lambda)
end
subject.increment
end
end