I have one class method, which use another one class instance method:
class Foo
def foo
# a lot of code here, which return String instance
end
end
class Bar
class UnknownType < StandardError;end
def initialize(foo)
self.foo = foo
end
attr_reader :foo
def call
# some code which use method foo
foo
end
private
def foo=(attr)
#foo ||= case attr
when Foo then attr.foo
when String then attr
else raise UnknownType, "Unknown type #{attr.class.name}"
end
end
end
And my test doesn't work, I try to sub methods:
- is_a
- kind_of?
let(:foo) { instance_double(Foo, foo: 'some text') }
let(:bar) { Bar.new(foo) }
subject { bar.call }
it 'make some business logic here' do
expect { subject }.to be_truthy
end
But it raise error UnknownType becasue template is a #<InstanceDouble(Foo) (anonymous)>
not a Foo
Case statements use === for case equality purposes and in this case Foo is the receiver not the argument. e.g.
case attr
when Foo then attr.foo
end
Compares attr to Foo as Foo === attr not the other way around.
So you could change your test to
it 'make some business logic here' do
allow(Foo).to receive(:===).with(foo).and_return(true)
expect { subject }.to be_truthy
end
This way when it evaluates your case statement it will follow the when Foo path because Foo === attr will be true due to the stubbing.
instance_double(Foo).class != Foo. As you have seen this returns an InstanceDouble object which won't work for the purpose of your comparison.
I would replace your instance_double line with manual instantiation and stubbing:
let(:foo) do
foo = Foo.new
allow(foo).to receive(:foo).and_return "some text"
foo
end
let(:bar) { Bar.new(foo) }
That way foo.class == Foo and it will work in your case statement properly.
Related
I was wondering how to pass a block to a method which will make the method return on yield.
The naive aproach doesn't work:
def run(&block)
block.call
end
run { return :foo } # => LocalJumpError
Wrapping in another proc has the same effect:
def run(&block)
proc { block.call }.call
end
run { return :bar } # => LocalJumpError
So I thought that the return statement is bound to the receiver of the current binding. However, trying it out with instance_eval proved me wrong:
class ProcTest
def run(&block)
puts "run: #{[binding.local_variables, binding.receiver]}"
instance_eval(&block)
end
end
pt = ProcTest.new
binding_inspector = proc { puts "proc: #{[binding.local_variables, binding.receiver]}" }
puts "main: #{[binding.local_variables, binding.receiver]}"
# => main: [[:pt, :binding_inspector], main]
binding_inspector.call
# => proc: [[:pt, :binding_inspector], main]
pt.run(&binding_inspector)
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => proc: [[:pt, :binding_inspector], #<ProcTest:0x007f4987b06508>]
pt.run { return :baz }
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => LocalJumpError
So the questions are:
How can this be done?
How is the return context tied to the return statement. Is this connection accessible via the language's API?
Was this implemented in such manner intentionally? If yes - why? If no - what are the obstacles to fix it?
I thought that the return statement is bound to the receiver of the current binding.
Only methods have an receiver. return is not a method:
defined? return #=> "expression"
Trying to invoke it as a method doesn't work:
def foo
send(:return, 123)
end
foo #=> undefined method `return'
trying it out with instance_eval proved me wrong
Though instance_eval evaluates the block in the context of the receiver (so you have access to the receivers instance methods and instance variables):
class MyClass
def foo(&block)
#var = 123
instance_eval(&block)
end
end
MyClass.new.foo { instance_variables }
#=> [:#var]
... it does not evaluate the block in the current binding (so you don't have access to any local variables):
class MyClass
def foo(&block)
var = 123
instance_eval(&block)
end
end
MyClass.new.foo { local_variables }
#=> []
How can this be done?
You could use eval, but that requires a string:
def foo
var = 123
eval yield
nil
end
foo { "return var * 2" }
#=> 246
Or by passing the binding to the block (again using eval):
def foo
var = 123
yield binding
nil
end
foo { |b| b.eval "return var * 2" }
#=> 246
return in a block returns from the enclosing method when the block is defined (ie, the closure in which the block is created). In your example, there is no enclosing block to return from, hence your exception.
This is easily demonstrated:
def foo(&block)
puts yield
puts "we won't get here"
end
def bar
foo { return "hi from the block"; puts "we never get here" }
puts "we never get here either"
end
puts bar # => "hi from the block" (only printed once; the puts in `foo` is not executed)
Return in a proc will immediately return out of the proc, not out of the method on the stack under the proc:
def foo(&block)
puts yield
puts "we will get here"
end
def bar
foo &->{ return "hi from the proc"; puts "we never get here" }
puts "we will get here too"
end
puts bar
# hi from the proc # puts from foo
# we will get here # puts from foo
# we will get here too # puts from bar
Because of these behaviors, there is no way to achieve your desired behavior, in which a return in the given block will execute a return in the method from which the block is invoked, unless the block was defined within that scope, since doing so would require one of the existing behaviors not work.
You could achieve something like this with throw...catch, which is kinda-sorta useful as a way to zip up the stack from an arbitrary depth, but you can't return arbitrary values with it:
def foo(&block)
yield
puts "we won't get here"
end
catch(:escape) do
foo &->{ throw :escape }
end
In the code below:
::Trace.tracer = ::Trace::ZipkinTracer.new()
what is the relation between Trace and ZipkinTracer?
ZipkinTracer is inside of Trace namespace, like this:
module Trace
class ZipkinTracer
# ...
end
end
The :: before constant name means that you point to the root. For example in the following code:
class Class1
end
module Module1
class Class1
end
def foo
::Class1
end
end
::Class1 ensures that you refer to the "root" Class1. If you had:
def foo
Class1
end
the Module1::Class1 would be referred.
This code does the following thing. First, it instantiates class ZipkinTracer:
new_instance = Trace::ZipkinTracer.new()
Then, it calls #tracer= method of the Trace module:
Trace.tracer=( new_instance )
Ruby syntax allows this to be rewritten as
Trace.tracer = new_instance
In this case, no assignment is happening, but a method ending in = is called. Methods ending in = are allowed in Ruby, used generally for attribute assignment, and they are special in that they always return the assigned value (that is, their argument), regardless of what other return value you might be trying to prescribe:
class Foo
def bar=( value )
puts "Method #bar= called!"
#bar = value
puts "Trying to return Quux!"
return "Quux!"
end
def bar; #bar end
end
foo = Foo.new
foo.bar #=> nil
foo.bar = "Baz!"
#=> Method #bar= called!
#=> Trying to return Quux!
#=> "Baz!" -- attempt to explicitly return "Quux!" failed
foo.bar #=> "Baz!"
In the code below:
::Trace.tracer = ::Trace::ZipkinTracer.new()
what is the relation between Trace and ZipkinTracer?
ZipkinTracer is inside of Trace namespace, like this:
module Trace
class ZipkinTracer
# ...
end
end
The :: before constant name means that you point to the root. For example in the following code:
class Class1
end
module Module1
class Class1
end
def foo
::Class1
end
end
::Class1 ensures that you refer to the "root" Class1. If you had:
def foo
Class1
end
the Module1::Class1 would be referred.
This code does the following thing. First, it instantiates class ZipkinTracer:
new_instance = Trace::ZipkinTracer.new()
Then, it calls #tracer= method of the Trace module:
Trace.tracer=( new_instance )
Ruby syntax allows this to be rewritten as
Trace.tracer = new_instance
In this case, no assignment is happening, but a method ending in = is called. Methods ending in = are allowed in Ruby, used generally for attribute assignment, and they are special in that they always return the assigned value (that is, their argument), regardless of what other return value you might be trying to prescribe:
class Foo
def bar=( value )
puts "Method #bar= called!"
#bar = value
puts "Trying to return Quux!"
return "Quux!"
end
def bar; #bar end
end
foo = Foo.new
foo.bar #=> nil
foo.bar = "Baz!"
#=> Method #bar= called!
#=> Trying to return Quux!
#=> "Baz!" -- attempt to explicitly return "Quux!" failed
foo.bar #=> "Baz!"
Let's say I have a class definition like so:
class Foo
def init(val)
#val = val
end
def self.bar
:bar
end
def val
#val
end
end
with a spec like:
describe Foo
it { should respond_to(:val) }
it { should respond_to(:bar) }
end
The second it assertion fails. It isn't clear to me from RSpec's documentation that respond_to should fail on class methods.
Nowadays it is suggested we use expect, like this:
describe Foo do
it 'should respond to :bar' do
expect(Foo).to respond_to(:bar)
end
end
See: http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
OLD ANSWER:
Actually you can make this approach by providing a subject:
describe Foo do
subject { Foo }
it { should respond_to :bar } # :bar being a class method
end
As described in here: http://betterspecs.org/#subject
Your example should be written like this:
it 'should respond to ::bar' do
Foo.should respond_to(:bar)
end
Consider the following test for rspec:
class RspecTest
def initialize
end
def to_s
"foo"
end
end
describe RspecTest do
it "should return foo (to_s)" do
RspecTest.new.should == "foo"
end
it "should return foo (inspect)" do
RspecTest.new.inspect == "foo"
end
end
And when tested through rspec:
%: rspec rspec_test.rb
F.
Failures:
1) RspecTest should return foo (to_s)
Failure/Error: RspecTest.new.should == "foo"
expected: "foo"
got: foo (using ==)
Diff:
# ./rspec_test.rb:13:in `block (2 levels) in <top (required)>'
Finished in 0.00059 seconds
2 examples, 1 failure
So the first test fails, whereas the second test passes. Why is that?
The second test passes, because it doesn't test anything. It doesn't contain any expectation (i.e. a call to should or should_not). It cannot fail, because there is nothing to fail.
The first test fails, because you are asserting that an instance of RspecTest is equal to the string 'foo'. This cannot possibly be true. How could those two objects possibly be equal if they aren't even the same kind of object?
Judging by the description of the test, you didn't actually mean to test whether the instance of RspecTest is equal to the string 'foo', but rather whether the return value of the instance method to_s is equal to the string 'foo'. However, you never call to_s anywhere.
Let's first fix the two obvious problems. Now, we have a test like this:
it 'should return foo (to_s)' do
RspecTest.new.to_s.should == 'foo'
end
it 'should return foo (inspect)' do
RspecTest.new.inspect.should == 'foo'
end
There is some unnecessary duplication there with the two RspecTest.new calls, so let's fix that by simply making RspecTest.new the default subject:
subject { RspecTest.new }
it 'should return foo (to_s)' do
subject.to_s.should == 'foo'
end
it 'should return foo (inspect)' do
subject.inspect.should == 'foo'
end
And actually, if you don't supply an explicit subject, then RSpec will walk up the chain of nested describe blocks until it finds a class, and will simply call that class's new method to provide the subject. So, we can just delete the subject declaration:
it 'should return foo (to_s)' do
subject.to_s.should == 'foo'
end
it 'should return foo (inspect)' do
subject.inspect.should == 'foo'
end
Personally, I prefer to let RSpec provide the example name by itself, so that the example name and the actual example code don't get out of sync, so I'd probably write that more like this:
describe RspecTest do
describe '#to_s' do
it { subject.to_s.should == 'foo' }
end
describe '#inspect' do
it { subject.inspect.should == "foo" }
end
end
Which yields:
RspecTest
#to_s
should == "foo"
#inspect
should == "foo"
Finished in 0.16023 seconds
2 examples, 0 failures
Last but not least, your initializer isn't actually doing anything, so you don't need it. All together, my version looks like this:
class RspecTest
def to_s; 'foo' end
end
describe RspecTest do
describe '#to_s' do
it { subject.to_s.should == 'foo' }
end
describe '#inspect' do
it { subject.inspect.should == "foo" }
end
end
I think your test should be the following (and they'll both pass). The first one is missing the actual to_s call, and the seocnd one is missing the .should:
describe RspecTest do
it "should return foo (to_s)" do
RspecTest.new.to_s.should == "foo"
end
it "should return foo (inspect)" do
RspecTest.new.inspect.should == "foo"
end
end