Ruby inspect vs to_s in Rspec - ruby

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

Related

Instance Variable in Ruby Resetting as Nil

My instance variables gets turned back to nil, even though it was set in a separate function that is called.
I've tried printing out before and after values for the instance variable, and have seen where it turns to nil. It's quite puzzling though. Attaching an example (https://repl.it/repls/FirebrickPresentKeyboard) also below:
class Test
def process
return if a.nil? && b.nil?
puts #some
end
def a
#some = nil
return true
end
def b
#some = "abc"
return false
end
end
class Test2
def process
return if c.nil?
puts #hello
end
def c
#hello = "hello"
return true
end
end
t = Test.new
t.process
t2 = Test2.new
t2.process
In the Test class, I expect #some to print "abc" since it is set during the "b" function. However, it prints nil.
In the Test2 class, I expect #hello to print "hello" and it certainly does.
In this example your method b never executes: && returns its first argument if it is falsy. Otherwise, it evaluates and returns its second argument. Since a.nil? evaluates to false the second expression never gets called.
Try this:
def process
return if a && b
puts #some
end

How to stub\mock instance in case statement?

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.

Ruby: Case using object

Is there a way to implicitly call methods on the object of a case statement?
IE:
class Foo
def bar
1
end
def baz
...
end
end
What I'd like to be able to do is something like this...
foo = Foo.new
case foo
when .bar==1 then "something"
when .bar==2 then "something else"
when .baz==3 then "another thing"
end
... where the "when" statements are evaluating the return of methods on the case object. Is some structure like this possible? I haven't been able to figure out the syntax if so...
FWIW, you don't need to pass an object to a case statement in 1.8.7 at all.
foo = Foo.new()
case
when foo.bar == this then that
when foo.baz == this then that
end
I was surprised as hegg.
http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-what-you-can-do-with-it/
What case .. when does is it calls the method === on your when values, passing your foo object as the argument to the === method. So in this code:
case foo
when 1 then "something"
when 2 then "something else"
when 3 then "another thing"
end
It will try 1 === foo, then 2 === foo, then 3 === foo, until one of them returns a truthy value.
One way of making case .. when more powerful is using Procs as the when values. I'm not sure about earlier versions of Ruby, but in 1.9, proc === x is equivalent to proc.call(x). So you can write code like this:
case foo
when Proc.new { foo.bar == 1 } then "something"
when Proc.new { foo.bar == 2 } then "something else"
when Proc.new { foo.baz == 3 } then "another thing"
end
Note that we don't even have to pass foo into the Procs, since we already have access to it. I don't think this is a very good choice of control structure for this example, a simple chain of ifs would make more sense:
if foo.bar == 1
"something"
elsif foo.bar == 2
"something else"
elsif foo.baz == 3
"another thing"
end
For different scenario where you want to test truthy method value of an object
class Foo
def approved?
false
end
def pending?
true
end
end
foo = Foo.new
case foo
when :approved?.to_proc
puts 'Green'
when :pending?.to_proc
puts 'Amber'
else
puts 'Grey'
end
# This will output: "Amber"
It looks like you're wanting to change the default receiver. This is hacky, but you could do something like:
string = Foo.new.instance_eval do
if bar==1 then "something"
elsif bar==2 then "something else"
elsif baz==3 then "another thing"
end
end
That's a big, terrible code smell, though, if you're just doing it because you're lazy. If you're doing it because you're creating a DSL, that's something else.

Ruby blocks with method_missing

Note, this is a follow up to my question here.
I'm trying to parse the following Tcl code:
foo bar {
biz buzz
}
In Tcl, foo is the method name, bar is the argument, and the rest is a "block" to be processed by eval.
Now here is my current implementation to this:
def self.foo(name, &block)
puts "Foo --> #{name}"
if block
puts "block exists"
else
puts "block does not exist"
end
end
def self.method_missing(meth, *args, &block)
p meth
p block
meth.to_s &block
end
tcl = <<-TCL.gsub(/^\s+/, "").chop
foo bar {
biz buzz
}
TCL
instance_eval(tcl)
Which outputs the following:
:bar
#<Proc:0x9e39c80#(eval):1>
Foo --> bar
block does not exist
In this example, when the block is passed up to the foo method, it does not exist. Yet in method_missing it does exist (at least it appears to exist). What's going on here?
Note, I am aware of ruby's precedence of parentheses and realize this works:
foo (bar) {
biz buzz
}
However, I want to have the parentheses omitted. So is this possible in ruby (without lexical analysis)?
You can do (I marked the lines I changed):
def self.foo args # changed
name, block = *args # changed
puts "Foo --> #{name}"
if block
puts "block exists"
else
puts "block does not exist"
end
end
def self.method_missing(meth, *args, &block)
p meth
p block
return meth.to_s, block # changed
end
That way, block will exist.
This has nothing to do with method_missing. You simply can't omit parentheses when passing block along with some parameters. In your case, Ruby will try to call bar method with block as an argument, and the result of it all will be passed to foo method as a single argument.
You can try this yourself by simplifying the method call (all the metaprogramming just obscures the real problem in your case):
# make a method which would take anything
def a *args, &block
end
# try to call it both with argument and a block:
a 3 {
4
}
#=>SyntaxError: (irb):16: syntax error, unexpected '{', expecting $end
# from /usr/bin/irb:12:in `<main>'
So the best solution I've found is to just gsub the string before processing it.
tcl = <<-TCL.gsub(/^\s+/, "").chop.gsub('{', 'do').gsub('}', 'end')
foo bar {
biz buzz
}
TCL

How do you override the ruby case equality operator? (===)

I have a class that I want to compare to both strings and symbols in a case statement, so I thought that I just override the ===() method for my class and all would be gold. However my ===() method never gets called during the case statement. Any ideas?
Here is some example code, and what happens in a irb session:
class A
def initialize(x)
#x=x #note this isn't even required for this example
end
def ===(other)
puts "in ==="
return true
end
end
irb(main):010:0> a=A.new("hi")
=> #
irb(main):011:0> case a
irb(main):012:1> when "hi" then 1
irb(main):013:1> else 2
irb(main):014:1> end
=> 2
(it never prints the message and should always return true anyway)
Note that ideally I'd like to do a
def ===(other)
#puts "in ==="
return #x.===(other)
end
Thanks in advance.
The expression after the 'case' keyword is the right hand side of the === expression, and the expression after the 'when' keyword is on the left hand side of the expression. So, the method that is being called is String.===, not A.===.
A quick approach to reversing the comparison:
class Revcomp
def initialize(obj)
#obj = obj
end
def ===(other)
other === #obj
end
def self.rev(obj)
Revcomp.new(obj)
end
end
class Test
def ===(other)
puts "here"
end
end
t = Test.new
case t
when Revcomp.rev("abc")
puts "there"
else
puts "somewhere"
end

Resources