How to mock a method call in Ruby - ruby

I'm writing a test method for Class A, method called m().
m() calls f() on Class B through an instance of B called 'b', but I mock that method call using-
def test_m
#a = A.new
b_mock = MiniTest::Mock.new
b_mock.expect(:f, 'expected_output')
def b_mock.f()
return 'expected output'
end
#a.b = b_mock
end
Now A has another method m1(), how can a mock a call to it and get a constant output, using the above or a better approach with Minitest?
Error-
NoMethodError: unmocked method :get_group_by_name, expected one of [:]

You can use MiniTest Object#stub method, it redefines the method result for the duration of the block.
require 'minitest/mock'
class A
def m1
m2
'the original result'
end
def m2
'm2 result'
end
end
#a = A.new
#a.stub :m1, "the stubbed result" do
puts #a.m1 # will print 'the stubbed result'
puts #a.m2
end
Read more: http://www.rubydoc.info/gems/minitest/4.2.0/Object:stub

Related

Instance variable in a class method

I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).

How do I change the context of lambda?

If you run the code below you get an error.
class C
def self.filter_clause param_1
puts param_1
yield # context of this param is class B
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->{ hi })
end
The error is
NameError: undefined local variable or method `hi' for B:Class
from (pry):17:in `block in <class:B>'
From my understanding the reason for this is the lambda is running under the context of class B. So it can't find the method def hi. I can't figure out how to get it to run under the context of class C.
Essentially I want to be able to inject a method being called in another class that accepts an argument and a block.
For example:
filter_clause("value", ->{ hi })
Is this possible?
Not sure if I am making sense.
You're close. If you want the lambda(/block, really) to execute in the context of an instance of C (since hi is an instance method on C), then you'll need to instantiate it and then instance_exec the block on that new instance:
class C
def self.filter_clause param_1, &block
puts new.instance_exec &block
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->{ hi })
end
# => hello
Are you opposed to passing the context into the block? If not, something like this would work:
class C
def self.filter_clause param_1
puts param_1
yield self
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->(klass){ klass.new.hi }) # "hello"
end

Ruby invoking attr method with array of attributes

Please explain me why i should define attr_list before attr? I can not understand why i should do that?
class Question
def self.attr_list
[:id, :name]
end
attr *self.attr_list
end
class Question
attr *self.attr_list
def self.attr_list
[:id, :name]
end
end
NoMethodError: undefined method `attr_list' for Question:Class
Unlike a def, a class is executed line by line immediately after you hit return to run your program:
class Dog
x = 10
puts x
end
--output:--
10
...
class Dog
puts x
x=10
end
--output:--
1.rb:2:in `<class:Dog>': undefined local variable or method `x'
In this line:
...
class Dog
def self.greet
puts 'hello'
end
greet
end
--output:--
hello
...
class Dog
greet
def self.greet
puts 'hello'
end
end
--output:--
1.rb:2:in `<class:Dog>': undefined local variable or method `greet'
Similarly, in this line:
attr *self.attr_list
you call self.attr_list(), yet the def comes after that line, so the method doesn't exist yet.
With a def, you can write this:
def do_math()
10/0
end
and you won't get an error until you call the method.
But by the time you create an instance of a class, all the code inside the class has already executed, creating the methods and constants that are defined inside the class.
By the way, you don't ever need to use attr because ruby 1.8.7+ has attr_accessor(reader and writer), attr_reader, and attr_writer.

How to use define_method inside initialize()

Trying to use define_method inside initialize but getting undefined_method define_method. What am I doing wrong?
class C
def initialize(n)
define_method ("#{n}") { puts "some method #{n}" }
end
end
C.new("abc") #=> NoMethodError: undefined method `define_method' for #<C:0x2efae80>
I suspect that you're looking for define_singleton_method:
define_singleton_method(symbol, method) → new_method
define_singleton_method(symbol) { block } → proc
Defines a singleton method in the receiver. The method parameter can be a Proc, a Method or an UnboundMethod object. If a block is specified, it is used as the method body.
If you use define_method on self.class, you'll create the new method as an instance method on the whole class so it will be available as a method on all instances of the class.
You'd use define_singleton_method like this:
class C
def initialize(s)
define_singleton_method(s) { puts "some method #{s}" }
end
end
And then:
a = C.new('a')
b = C.new('b')
a.a # puts 'some method a'
a.b # NoMethodError
b.a # NoMethodError
b.b # puts 'some method b'
If your initialize did:
self.class.send(:define_method,n) { puts "some method #{n}" }
then you'd get:
a.a # puts 'some method a'
a.b # puts 'some method b'
b.a # puts 'some method a'
b.b # puts 'some method b'
and that's probably not what you're looking for. Creating a new instance and having the entire class change as a result is rather odd.
Do as below :
class C
def initialize(n)
self.class.send(:define_method,n) { puts "some method #{n}" }
end
end
ob = C.new("abc")
ob.abc
# >> some method abc
Module#define_method is a private method and also a class method.Your one didn't work,as you tried to call it on the instance of C.You have to call it on C,using #send in your case.
You were almost there. Just point to the class with self.class, don't even need to use :send:
class C
def initialize(n)
self.class.define_method ("#{n}") { puts "some method #{n}" }
end
end
ob = C.new('new_method')
ob2 = C.new('new_method2')
# Here ob and ob2 will have access to new_method and new_method2 methods
You can also use it with :method_missing to teach your class new methods like this:
class Apprentice
def method_missing(new_method)
puts "I don't know this method... let me learn it :)"
self.class.define_method(new_method) do
return "This is a method I already learned from you: #{new_method}"
end
end
end
ap = Apprentice.new
ap.read
=> "I don't know this method... let me learn it :)"
ap.read
=> "This is a method I already learned from you: read"

Import into local scope

Consider
# sun.rb
class Sunshine
def bright?
return true
end
end
def greeting(greeter)
puts "hello, Sun from #{greeter}"
end
# main.rb
def abc
my_load "sun.rb"
greeting("abc")
return Sunshine.new
end
s = abc
puts s.bright?
greeting("Adrian")
...
Can I have such a my_load here that the greeting("abc") call succeeds, but the latter greeting("Adrian") causes a NoMethodError; but the puts s.bright? call succeeds.
So, synthetically speaking: such that classes,methods from sun.rb are in the scope of the caller of my_load and so that they additionally get garbage collected when not referenced anymore?
First off, a stand-alone (called on the main object) method call will cause a NameError exception if it doesn't exist. You will get a NoMethodError only if you call the method on an object.
nothing #=> NameError
class A; end
A.nothing #=> NoMethodError
This is because when you call nothing on main, it doesn't know if it is a method or a variable. However:
nothing() #=> NoMethodError
Because with the () it now knows it is a method you are trying to call. Just something to watch out for.
Second, if you want a method to work and then not work, use undef.
def greeting(name)
puts "Hello, #{name}"
end
greeting("Chell") #=> "Hello, Chell"
undef greeting
greeting("Chell") #=> NoMethodError

Resources