Why is rspec not seeing these class methods? - ruby

If I have ruby file maze.rb with
class Maze
def self.x
end
def self.y
end
end
and a rspec file with
require 'maze'
describe "A Maze" do
it "exists" do
expect(Maze).to be
end
it " has x-y dimension" do
expect(Maze.x).to be
expect(Maze.y).to be
end
end
Why does the test for Maze.x fail ?
Failures:
1) A Maze has x-y dimension
Failure/Error: expect(Maze.x).to be
expected nil to evaluate to true
# ./spec/maze_spec.rb:8:in `block (2 levels) in <top (required)>'

It is working.
What's happening is that the class level method isn't doing anything and thus returns nil - as opposed to method not found. Simply adding true as the return value resolves this, i.e.
def x
true
end

Related

Prevent object from being extended

I have a module that provides some lazy lookup via dot syntax for hashes:
module DotHash
def method_missing(method_name, *args, &block)
# look for keys...
end
def respond_to_missing?(method_name, _include_all = nil)
# some logic
end
end
I ran into the problem of accidentally extending nil:
# #hash == nil
#hash.extend(DotHash)
and this caused HUGE problems, because now nil has this method_missing logic which messes things up.
I though adding a hook would solve the problem:
module DotHash
def self.extended(base)
return if base.is_a?(Hash)
raise "must be a hash"
end
def method_missing(method_name, *args, &block)
# look for keys...
end
def respond_to_missing?(method_name, _include_all = nil)
# some logic
end
end
And indeed, it throws an error:
[1] pry(main)> nil.extend(DotHash)
RuntimeError: must be a hash
But the logic got added anyway:
[2] pry(main)> nil.foobar
Traceback (most recent call last):
9707: from bin/rails:6:in `<main>'
9706: from /usr/local/bundle/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require'
9705: from /usr/local/bundle/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency'
9704: from /usr/local/bundle/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require'
9703: from /usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
9702: from /usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
9701: from /usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
9700: from /usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
... 9695 levels...
4: from /usr/src/app/app/lib/dot_hash.rb:26:in `respond_to_missing?'
3: from /usr/src/app/app/lib/dot_hash.rb:14:in `method_missing'
2: from /usr/src/app/app/lib/dot_hash.rb:26:in `respond_to_missing?'
1: from /usr/src/app/app/lib/dot_hash.rb:14:in `method_missing'
/usr/src/app/app/lib/mapper/dot_hash.rb:26:in `respond_to_missing?': stack level too deep (SystemStackError)
Is there a hook that get's triggered BEFORE the object is extended, and not after?
You can override extend_object: (the docs contain a similar example)
Extends the specified object by adding this module's constants and methods (which are added as singleton methods). This is the callback method used by Object#extend.
module DotHash
def self.extend_object(obj)
raise TypeError, 'must be a hash' unless obj.is_a?(Hash)
super
end
def foo
123
end
end
h = {}
h.extend(DotHash)
h.foo #=> 123
n = nil
n.extend(DotHash) # TypeError: must be a hash
n.foo # NoMethodError: undefined method `foo' for nil:NilClass

rspec failing error: expected false to respond to `false?`

I am running this portion of a test:
describe Dictionary do
before do
#d = Dictionary.new
end
it 'can check whether a given keyword exists' do
#d.include?('fish').should be_false
end
With this code:
class Dictionary
def initialize
#hash = {}
end
def add(new_entry)
new_entry.class == String ? #hash[new_entry] = nil : new_entry.each { |noun, definition| #hash[noun] = definition}
end
def entries
#hash
end
def keywords
#hash.keys
end
def include?(word)
if #hash.has_key?(word)
true
else
false
end
end
end
I don't know what I'm doing wrong, but my tests keep failing and saying this:
> 1) Dictionary can check whether a given keyword exists
> Failure/Error: #d.include?('fish').should be_false
> expected false to respond to `false?`
I am confused at the error since it seems to be giving the correct answer. I would really appreciate if someone could take a few minutes to tell me what's wrong with my code.
Thank you tons.
If you browse the RSpec Expectations 2.99 and RSpec Expectations 2.14 and search the section - Truthiness and existentialism, you will find
expect(actual).to be_true # passes if actual is truthy (not nil or false)
expect(actual).to be_false # passes if actual is falsy (nil or false)
# ...............
# ...
But of you browse RSpec Expectations 3.0 , the above method names got changed to -
expect(actual).to be_truthy # passes if actual is truthy (not nil or false)
expect(actual).to be true # passes if actual == true
expect(actual).to be_falsey # passes if actual is falsy (nil or false)
# ...........
#......
It seems you are in 3.0, and using the method which were exist prior to this version. Thus you were getting the error.
I put the code in my test.rb file as below :-
class Dictionary
def initialize
#hash = {}
end
def add(new_entry)
new_entry.class == String ? #hash[new_entry] = nil : new_entry.each { |noun, definition| #hash[noun] = definition}
end
def entries
#hash
end
def keywords
#hash.keys
end
def include?(word)
if #hash.has_key?(word)
true
else
false
end
end
end
And my spec/test_spec.rb file is -
require_relative "../test.rb"
describe Dictionary do
before do
#d = Dictionary.new
end
it 'can check whether a given keyword exists' do
#d.include?('fish').should be_false
end
end
Now I am running the code from my console, and it works :
arup#linux-wzza:~/Ruby> rspec -v
2.14.8
arup#linux-wzza:~/Ruby> rspec spec
.
Finished in 0.00169 seconds
1 example, 0 failures
Now I am changing the code in my spec/test_spec.rb file :-
require_relative "../test.rb"
describe Dictionary do
before do
#d = Dictionary.new
end
it 'can check whether a given keyword exists' do
#d.include?('fish').should be_falsey
end
end
and again run the test :-
arup#linux-wzza:~/Ruby> rspec -v
2.14.8
arup#linux-wzza:~/Ruby> rspec spec
F
Failures:
1) Dictionary can check whether a given keyword exists
Failure/Error: #d.include?('fish').should be_falsey
NoMethodError:
undefined method `falsey?' for false:FalseClass
# ./spec/test_spec.rb:9:in `block (2 levels) in <top (required)>'
Finished in 0.00179 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/test_spec.rb:8 # Dictionary can check whether a given keyword exists
arup#linux-wzza:~/Ruby>
Now, they also mentioned in the 3.0.0.beta1 / 2013-11-07 changelog
Rename be_true and be_false to be_truthy and be_falsey. (Sam Phippen)

undefined local variable or method error when trying to use `instance_variable_set` in rspec

I have a class like this
require 'net/http'
class Foo
def initialize
#error_count = 0
end
def run
result = Net::HTTP.start("google.com")
#error_count = 0 if result
rescue
#error_count += 1
end
end
And this is a spec file for it.
require_relative 'foo'
describe Foo do
let(:foo){ Foo.new}
describe "#run" do
context "when fails 30 times" do
foo.instance_variable_set(:#error_count, 30)
end
end
end
And run rspec foo_spec.rb, then fails with this error.
foo_spec.rb:7:in `block (3 levels) in <top (required)>': undefined local variable or method `foo' for #<Class:0x007fc37410c400> (NameError)
How should I call instance_variable_set method in rspec?
Edit
I want to call send_error method if 30 times fails.
require 'net/http'
class Foo
def initialize
#error_count = 0
end
def run
result = Net::HTTP.start("google.com")
#error_count = 0 if result
rescue
#error_count += 1
send_error if #error_count >= 30
end
def send_error
end
end
And spec file to test that send_error is called when connection fails 30 times.
require_relative 'foo'
describe Foo do
let(:foo){ Foo.new}
describe "#run" do
context "when fails 30 times" do
it "should send error" do
foo.instance_variable_set(:#error_count, 30)
expect(foo).to receive(:send_error)
end
end
end
end
I don't know what you're trying to do, but I suspect it's not the right approach to do things.
However, your immediate problem is that you're not in the context of a test, so foo is undefined. You want to wrap your foo.instance_variable_set in a test construct - either an it or specify block, or a before :each, or something similar.

How to implement Ruby utility class methods?

Need help implementing Ruby utility class methods to get this test to pass. Can someone break this down for me please?
My Code
class Temperature
class << self
def from_fahrenheit temp
Temperature.new({f: temp})
end
def from_celsius temp
Temperature.new({c: temp})
end
end
def initialize(options={})
#f = options[:f]
#c = options[:c]
end
def in_fahrenheit
return #f if #f
(#c * (9.0 / 5.0)) + 32
end
def in_celsius
return #c if #c
(#f - 32) * 5.0 / 9.0
end
end
class Celsius < Temperature
def initialize temp
#temp = temp
end
end
class Fahrenheit < Temperature
def initialize temp
#temp = temp
end
end
Failures:
1) Temperature Temperature subclasses Celsius subclass is constructed in degrees celsius
Failure/Error: (#f - 32) * 5.0 / 9.0
NoMethodError:
undefined method `-' for nil:NilClass
# ./ct.rb:1118:in `in_celsius'
# ./ct.rb:1219:in `block (4 levels) in <top (required)>'
2) Temperature Temperature subclasses Fahrenheit subclass is constructed in degrees fahrenheit
Failure/Error: (#c * (9.0 / 5.0)) + 32
NoMethodError:
undefined method `*' for nil:NilClass
# ./ct.rb:1113:in `in_fahrenheit'
# ./ct.rb:1230:in `block (4 levels) in <top (required)>'
Rspec Test
describe "utility class methods" do
end
# Here's another way to solve the problem!
describe "Temperature subclasses" do
describe "Celsius subclass" do
it "is constructed in degrees celsius" do
Celsius.new(50).in_celsius.should == 50
Celsius.new(50).in_fahrenheit.should == 122
end
it "is a Temperature subclass" do
Celsius.new(0).should be_a(Temperature)
end
end
describe "Fahrenheit subclass" do
it "is constructed in degrees fahrenheit" do
Fahrenheit.new(50).in_fahrenheit.should == 50
Fahrenheit.new(50).in_celsius.should == 10
end
it "is a Temperature subclass" do
Fahrenheit.new(0).should be_a(Temperature)
end
end
end
end
You aren't using the Temperature class how you defined it. You take an options hash with :f and :c keys in it in the Temperature class, but don't set those in your sub-classes.
Try this:
class Celsius < Temperature
def initialize temp
super(c: temp)
end
end
class Fahrenheit < Temperature
def initialize temp
super(f: temp)
end
end
Is this for an exercise or something? It's an ... interesting design.

Recursive lambdas in Ruby

I have the following code which correctly generates all possible trees of size num:
class Tree
attr_accessor :left, :right
def initialize left = nil, right = nil
#left = left
#right = right
end
# Don't ever specify any arguments, it will make me very angry.
# Tilt your head 90 degrees to the side to see the tree when viewing.
def print level = 0
#right.pretty_print(level + 1) if #right
puts (' ' * level) + to_s
#left.pretty_print(level + 1) if #left
end
def self.generate num
trees = []
generate_subtrees(num) { |tree| trees << tree } if num > 0
trees
end
private
def self.generate_subtrees num, &block
if num == 0
yield nil
else
(1..num).each do |root_position|
generate_subtrees(root_position - 1) do |left|
generate_subtrees(num - root_position) do |right|
yield Tree.new nil, left, right
end
end
end
end
end
end
I’m trying to (for the sake of it) “condense” this into one method, utilizing lambda recursion. My current attempt (of several iterations) is below:
def self.generate num
trees = []
gen = ->(num, &block) do
if num == 0
yield nil # L61
else
(1..num).each do |root_position| # L63
gen.call(root_position - 1) do |left| # L64
gen.call(num - root_position) do |right|
block.call { Tree.new nil, left, right }
end
end
end
end
end
gen.call(num) { |tree| trees << tree } # L73
trees
end
This results in the error (referenced lines noted above):
LocalJumpError: no block given (yield)
from tree.rb:61:in `block in generate'
from tree.rb:64:in `call'
from tree.rb:64:in `block (2 levels) in generate'
from tree.rb:63:in `each'
from tree.rb:63:in `block in generate'
from tree.rb:73:in `call'
from tree.rb:73:in `generate'
from (irb):4
from /Users/amarshall/.rbenv/versions/1.9.2-p290/bin/irb:12:in `<main>'
What am I doing wrong? Alternative solutions to this mostly academic problem are also welcome.
The yield keyword does not work from inside a lambda. The alternative is to use &block, in the same way that you are already doing on line 64 and 65:
gen = ->(num, &block) do
if num == 0
block.call(nil)
else
# ...
end
gen.call(num) { |tree| trees << tree }

Resources