Minitest how to solve mocked method :call expects 0 arguments, got 1 - ruby

I have a service with the call method to stub. But its always have this error:
SomeTest#test_1:
ArgumentError: mocked method :call expects 0 arguments, got 1
test.rb:17:in `block in test_1'
test.rb:16:in `test_1'
Here's the code:
require 'minitest/autorun'
class SomeService
def initialize(headers = {})
end
def call
end
end
class SomeTest < Minitest::Test
def test_1
mock = Minitest::Mock.new
mock.expect :call, nil
SomeService.stub :new, mock do
SomeService.new({}).call
end
end
end
What did I do wrong? thanks

In general what you wrote is right but you hit and edge case with the method name call.
As you can see in the documentation of stub method.
"If val_or_callable responds to #call, then it returns the result of calling it".
So basically the problem is your mock responds to call, and the stub method is calling it before entering the block.
You can solve it by wrapping the mock in a lambda that discards the arguments.
require 'minitest/autorun'
class SomeService
def initialize(headers = {})
end
def call
end
end
class SomeTest < Minitest::Test
def test_1
mock = Minitest::Mock.new
mock.expect :call, nil
SomeService.stub :new, ->(_args) { mock } do
SomeService.new({}).call
end
end
end

Related

Calling methods from a different class on method_missing

I have a class that loads a collection and loads items into a collection based on a certain criteria
require_relative 'thing'
class Someclass
def self.tings
things=load_things()
end
def self.select_things(something)
return things.select { |thing| thing.some_property == something }
end
end
I would like to use method_missing instead of the direct defintions
require_relative 'thing'
class Someclass
def self.method_missing(method, *args)
things=load_things()
if method == 'select_things'
self.send("things.select { |thing| thing.some_property == args }")
end
end
end
However, this approach doesn't work and method_missing just outputs the code string. Is there a proper way to call a code from method_missing?
Thank you very much everyone in advance
There are two issues with your method_missing implementation:
The method name is given as a symbol, not as a string:
def self.method_missing(method, *args)
if method == :select_things
# ...
end
end
You have to call super if you don't process the message yourself:
def self.method_missing(method, *args)
if method == :select_things
# ...
else
super
end
end
If you don't call super your object is going to swallow any message without ever raising a NoMethodError and you'll have a very hard time understanding why your code isn't working.
In addition, you should also implement respond_to_missing? to return true for the messages you are responding to, e.g.:
def self.respond_to_missing?(method, include_all = false)
[:select_things].include?(method) || super
end
The above gives you:
Someclass.respond_to?(:select_things) #=> true

How to mock a method call in 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

Ruby MiniTest UnitTest Stubbing Class method just for one test

I want to stub a class method for just one test and for the rest of the tests, i want the actual method to be called. I have always been using rspec and mocha, so the below behavior looks bizarre.
The class that i want to stub in one of my tests.
class MyClass
def self.foo(arg)
return "foo#{arg}"
end
end
The test where i try to stub MyClass.foo
class XYZTest < Minitest::Test
def test_1
MyClass.expects(:foo).returns('abcd')
assert_equal MyClass.foo('123'), 'abcd'
end
def test_2
assert_equal MyClass.foo('123'), 'foo123'
end
end
The first test passes, but the second test fails stating Mocha::ExpectationError: unexpected invocation: MyClass.foo('123')
In the test_2, i want the actual class method to be called instead of the stub that i did in test_1.
PS: Above is a striped down example. I do not want to reset everytime, I stub the class method.
Minitest stubs methods within a block, so what you're trying to do is simple.
class XYZTest < Minitest::Test
# stubbed here
def test_1
MyClass.stub(:foo, 'abcd') do
assert_equal MyClass.foo('123'), 'abcd'
end
end
# not stubbed here
def test_2
assert_equal MyClass.foo('123'), 'foo123'
end
end

How can I test delegating methods using SimpleDelegator and RSpec?

I'm using Ruby 1.9.3 and trying to make some tests with RSpec.
I have a class:
class A
def method1
"test"
end
end
class B < SimpleDelegator
def initialize(events)
#events = events
end
end
Now I'm trying to test delegation behaviour:
require 'spec_helper'
RSpec.describe B do
let(:a) { A.new }
let(:b) { B.new(a) }
it "Should delegate unknown calls to A object" do
expect(b.method1).not_to eq(nil)
end
end
I get the following error:
NoMethodError:
undefined method `method1' for nil:B
Seems that the test would pass if add method_missing manually:
class B < SimpleDelegator
def initialize(events)
#events = events
end
def method_missing(meth, *args, &blk)
#events.send(meth, *args, &blk)
end
end
What I'm doing wrong here?
Thanks
The problem is that you added a initializer to the class B without calling super and passing the instance you want to decorate. Your code should look like this:
class A
def method1
"test"
end
end
class B < SimpleDelegator
def initialize(events)
#events = events
super(events)
end
end
You don't need to define an initialize method on B. SimpleDelegator defines one for you. When you defined your own initialize method, you overrode the initialize method you inherited from the SimpleDelegator class.
Try this:
class A
def method1
"test"
end
end
class B < SimpleDelegator
end
This is from irb: B.new(A.new).method1 #=> "test"
You could define your own initialize method and call super, but I wouldn't unless you really had to.

Ruby - Executing same code after most methods in a class

I'm trying to create some kind of module or superclass that wraps one method call after each method of the subclass.
There are some constraints though: I wouldn't want the method to be run after initialize() is called nor after a few other methods of my choice is called.
Another constraint is that I would only want that method to be executed IF the flag #check_ec is set to true.
I have classes with more than 60 methods that I have hard-coded the same piece of code that ispasted all over the place.
Is there a way that I could make a wrapper that would automatically execute that method for my class methods?
So the idea is this:
class Abstract
def initialize(check_ec)
#check_ec = check_ec
end
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[EC]}"
end
def methods(method) # below each method it would execute after
result = method() # execute the given method normally
after(result) if #check_ec and method != :initialize and method != :has_valid_params
end
end
class MyClass < Abstract
def initialize(name, some_stuff, check_error_code)
# do some stuff...
#name = name
super(check_error_code)
end
def my_method_a() # execute after() after this method
return {EC: 0}
end
def my_method_b() # execute after() after this method
return {EC: 7}
end
def has_valid_params() # don't execute after() on this method
return true
end
end
This is trivially easy using method_missing, and composition instead of inheritance. You can build a very simple class which forwards method invocations, and then executes an after callback, except for specific method names:
class Abstract
def initialize(object)
#object = object
end
def method_missing(method, *arguments)
result = #object.send(method, *arguments)
after() unless method == "has_valid_params"
result
end
def after
# whatever
end
end
o = Abstract.new(MyClass.new)
A solution using singleton class.
class MyClass
def initialize(name, some_stuff)
# do some stuff...
#name = name
end
def my_method_a # execute after() after this method
return {EC: 0}
end
def my_method_b() # execute after() after this method
return {EC: 7}
end
def has_valid_params() # don't execute after() on this method
return true
end
end
module ErrorCodeChecker
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[:EC]}"
end
def addErrorCodeCheck(exclude = [])
methods = self.class.superclass.public_instance_methods(false) - exclude
class << self
self
end.class_exec {
methods.each {|method|
define_method(method) {|*p|
super(*p).tap {|res| after(res)}
}
}
}
end
end
class MyClassEC < MyClass
include ErrorCodeChecker
def initialize(name, some_stuff, check_error_code, exclude = [])
super name, some_stuff
addErrorCodeCheck(exclude) if check_error_code
end
end
'addErrorCodeCheck' opens up the singleton class of an instance of MyClassEC, and redefines instance methods of MyClass not in the exclude list. The redefined methods hide the original methods but call them via 'super' method inside before calling 'after'.
You can apply 'addErrorCodeCheck' repeatedly later if needed.
Execution example: (tested in Ruby 1.9.3)
my = MyClassEC.new('test', 'abc', true, [:has_valid_params])
my.my_method_a # => ERROR CODE: 0
my.my_method_b # => ERROR CODE: 7
my.has_valid_params # => (nothing)
What about this? It has a major drawback which is that your methods must be already defined before calling check_error_code, but it may suit your needs. You could look for inspiration for a better solution in Rails callbacks, or defer the redefinition of each method until that method is added using the method_added hook.
Include ErrorCodeChecker and call check_error_code in each class you want to check the error code (as in the last line of the snippet).
module ErrorCodeChecker
def self.included(base)
base.send(:extend, ClassMethods)
end
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[:ec]}"
end
module ClassMethods
def check_error_code(options = {})
check_on = instance_methods(false) - Array(options[:except])
check_on &= Array(options[:only]) if options[:only]
class_eval do
check_on.each do |method|
alias_method "#{ method }_without_ec", method
define_method(method) do |*args, &block|
send("#{ method }_without_ec", *args, &block).tap { |result| after(result) if #check_ec }
#if you want to actually return the return value of calling after:
#result = send("#{ method }_without_ec")
##check_ec ? after(result) : result
end
end
end
end
end
end
class Abstract
include ErrorCodeChecker
def initialize(check_ec)
#check_ec = check_ec
end
end
class MyClass < Abstract
def initialize(name, some_stuff, check_error_code)
# do some stuff...
#name = name
super(check_error_code)
end
def my_method_a # execute after() after this method
{ec: 0}
end
def my_method_b # execute after() after this method
{ec: 7}
end
def has_valid_params # don't execute after() on this method
true
end
check_error_code except: :has_valid_params
#or whitelisting:
#check_error_code only: [:my_method_a, :my_method_b]
#or both:
#check_error_code only: :my_method_a, except: [:has_valid_params, dont_check_this_one]
end

Resources