Stub and Mock Minitest - ruby

I'm trying to implement and learn testing (seems like minitest is the way to go). And I am failing miserably to test a internal module class method.
Here's more or less the usecase I am trying to do. (And maybe I am going at this completely the wrong way)
module Zombie
class << self
# This is the method/code I want to test/execute
def intimidate
roar('slardar')
end
# This is the method that is internal, that I want to stub.
# Actual method(not this mocked one) is stateful. So I want to have
# mocked predefined data.
def roar(a)
'rawrger' + a
end
end
end
# Test Thingy
class ZombieTest < Minitest::Test
def test_mr_mock
#mock = Minitest::Mock.new
#mock.expect(:roar, 'rawrgerslardar', ['slardar'])
Zombie.stub :roar, #mock do
Zombie.intimidate
end
#mock.verify
end
end

You can use a lambda to pass the parameter:
class ZombieTest < Minitest::Test
def test_mr_mock
#mock = Minitest::Mock.new
#mock.expect(:roar, 'rawrgerslardar', ['slardar'])
Zombie.stub :roar, ->(a) { #mock.roar(a) } do
Zombie.intimidate
end
#mock.verify
end
end

Related

How should I call object methods from a static method in a ruby class?

From an academic / "for interests sake" (best practice) perspective:
In a Ruby class, I want to provide a static method that calls instance methods of the class. Is this considered an acceptable way of doing it, or is there is a "better" way?
class MyUtil
def apiClient
#apiClient ||= begin
apiClient = Vendor::API::Client.new("mykey")
end
end
def self.setUpSomething(param1, param2, param3=nil)
util = self.new() # <-- is this ok?
util.apiClient.call("foo", param2)
# some logic and more util.apiClient.calls() re-use client.
end
end
And then I can use this lib easily:
MyUtil.setUpSomething("yes","blue")
vs
MyUtil.new().setupUpSomething()
# or
util = MyUtil.new()
util.setUpSomething()
The environment is sys admin scripts that get executed in a controlled manner, 1 call at a time (i.e. not a webapp type of environment that could be subjected to high load).
Personally I would probably do something like:
class MyUtil
API_KEY = "mykey"
def apiClient
#apiClient
end
def initialize
#apiClient = Vendor::API::Client.new(API_KEY)
yield self if block_given?
end
class << self
def setUpSomthing(arg1,arg2,arg3=nil)
self.new do |util|
#setup logic goes here
end
end
def api_call(arg1,arg2,arg3)
util = setUpSomthing(arg1,arg2,arg3)
util.apiClient.call("foo", param2)
#do other stuff
end
end
end
The difference is subtle but in this version setUpSomthing guarantees a return of the instance of the class and it's more obvious what you're doing.
Here setUpSomthing is responsible for setting up the object and then returning it so it can be used for things, this way you have a clear separation of creating the object and setting the object up.
In this particular case you likely need a class instance variable:
class MyUtil
#apiClient = Vendor::API::Client.new("mykey")
def self.setUpSomething(param1, param2, param3=nil)
#apiClient.call("foo", param2)
# some logic and more util.apiClient.calls() re-use client.
end
end
If you want a lazy instantiation, use an accessor:
class MyUtil
class << self
def api_client
#apiClient ||= Vendor::API::Client.new("mykey")
end
def setUpSomething(param1, param2, param3=nil)
apiClient.call("foo", param2)
# some logic and more util.apiClient.calls() re-use client.
end
end
end
Brett, what do you think about this:
class MyUtil
API_KEY = 'SECRET'
def do_something(data)
api_client.foo(data)
end
def do_something_else(data)
api_client.foo(data)
api_client.bar(data)
end
private
def api_client
#api_client ||= Vendor::API::Client.new(API_KEY)
end
end
Usage
s = MyUtil.new
s.do_something('Example') # First call
s.do_something_else('Example 2')

Ruby - how to test method using minitest

I have this class:
require 'yaml'
class Configuration
class ParseError < StandardError; end
attr_reader :config
def initialize(path)
#config = YAML.load_file(path)
rescue => e
raise ParseError, "Cannot open config file because of #{e.message}"
end
def method_missing(key, *args, &block)
config_defines_method?(key) ? #config[key.to_s] : super
end
def respond_to_missing?(method_name, include_private = false)
config_defines_method?(method_name) || super
end
private
def config_defines_method?(key)
#config.has_key?(key.to_s)
end
end
how do I write test for methods: method_missing, respond_to_missing?, config_defines_method?
I have some understanding about unit testing but when it comes to Ruby im pretty new.
So far i have tried this:
def setup
#t_configuration = Configuration.new('./config.yaml')
end
def test_config_defines_method
#t_configuration.config[:test_item] = "test"
assert #t_configuration.respond_to_missing?(:test_item)
end
Im not sure if im testing it right, because when i run rake test it gives me this:
NoMethodError: private method `respond_to_missing?' called for #
If there is no clear way how to solve this, can anyone direct me to a place where similar tests are written? So far Ive only found hello world type of test examples which are not helping much in this case.
As mentioned in the documentation for #respond_to_missing?, you do not want to call the method directly. Instead, you want to check that the object responds to your method. This is done using the #respond_to? method:
assert #t_configuration.respond_to?(:test_item)

How to 'disable' a certain method in ruby from being called in the console

Let's say I have a class ABC with two methods.
class ABC
def test
"test"
end
def display_test
puts test
end
end
I only want to be able to call ABC.new.display_test from my console (IRB) (returning me 'test') and not be able to call ABC.new.test or ABC.new.send(:test) for that matter. Is this possible? If so, how?
The most thorough way to do so would be to have test private and override the send method to specifically block the call to test:
class ABC
def test
"test"
end
private :test
def display_test
puts test
end
def send(id)
if id == :test
raise NoMethodError.new("error")
else
super(id)
end
end
alias_method :__send__, :send
end
Please note that this send override is not a proper one, as it only accepts a single parameter.
If done the right way, it would become possible to do something like this:
ABC.new.send(:send, :test)
Make your method private.
One way to do this is:
class Foo
private
def bar
# …
end
end
Another way is:
class Foo
def bar
# …
end
private :bar
end
Note that this won't entirely prevent the method from getting called: it'll only stop "normal" calls. Indeed, there are many ways to call a private method from outside of a class in Ruby. To list a few off the top of my head:
Foo.new.send :bar
Foo.new.__send__ :bar
Foo.new.instance_eval { bar }
You could try to enumerate every possible way to send a message to a method, but your efforts will likely be fruitless in the end: Ruby is far too open for you to be able to seal the gates. So stick to making it private.
This is partly possible. You can remove it from being called directly, but not through send. This is the way that Ruby's private methods work.
To prevent it from being called directly, make it private.
private :test
class TestClass
def display_test
puts test
end
private
def test
"test"
end
end
This is a full example:
puts TestClass.new.display_test
puts TestClass.new.send(:test)
begin
puts TestClass.new.test
rescue
puts "Error!"
end
Ruby Private Methods
You can use undef method to do disable any methods, but in this case, you can't call it anywhere.
class ABC
def test
"test"
end
def display_test
puts test
end
undef :display_test
end

How do I mock a Class with Ruby?

I'm using minitest/mock and would like to mock a class. I'm not trying to test the model class itself, but rather trying to test that a service (SomeService) interacts with the model (SomeModel).
I came up with this (Hack::ClassDelegate), but I'm not convinced it's a good idea:
require 'minitest/autorun'
require 'minitest/mock'
module Hack
class ClassDelegate
def self.set_delegate(delegate); ##delegate = delegate; end
def self.method_missing(sym, *args, &block)
##delegate.method_missing(sym, *args, &block)
end
end
end
class TestClassDelegation < MiniTest::Unit::TestCase
class SomeModel < Hack::ClassDelegate ; end
class SomeService
def delete(id)
SomeModel.delete(id)
end
end
def test_delegation
id = '123456789'
mock = MiniTest::Mock.new
mock.expect(:delete, nil, [id])
SomeModel.set_delegate(mock)
service = SomeService.new
service.delete(id)
assert mock.verify
end
end
I'm pretty sure that mocking a class is not a great idea anyway, but I have a legacy system that I need to write some tests for and I don't want to change the system until I've wrapped some tests around it.
I think that's a little complicated. What about this:
mock = MiniTest::Mock.new
SomeService.send(:const_set, :SomeModel, mock)
mock.expect(:delete, nil, [1])
service = SomeService.new
service.delete(1)
mock.verify
SomeService.send(:remove_const, :SomeModel)
After running into the same problem and thinking about it for quite a while, I found that temporarily changing the class constant is probably the best way to do it (just as Elliot suggests in his answer).
However, I found a nicer way to do it: https://github.com/adammck/minitest-stub-const
Using this gem, you could write your test like this:
def test_delegation
id = '123456789'
mock = MiniTest::Mock.new
mock.expect(:delete, nil, [id])
SomeService.stub_const 'SomeModel', mock do
service = SomeService.new
service.delete(id)
end
assert mock.verify
end

Mocking a dynamically-generated class in ruby metaprogramming with rspec

I'm new to TDD and metaprogramming so bear with me!
I have a Reporter class (to wrap the Garb ruby gem) that will generate a new report class on-the-fly and assign it to a GoogleAnalyticsReport module when I hit method_missing. The main gist is as follows:
# Reporter.rb
def initialize(profile)
#profile = profile
end
def method_missing(method, *args)
method_name = method.to_s
super unless valid_method_name?(method_name)
class_name = build_class_name(method_name)
klass = existing_report_class(class_name) ||
build_new_report_class(method_name, class_name)
klass.results(#profile)
end
def build_new_report_class(method_name, class_name)
klass = GoogleAnalyticsReports.const_set(class_name, Class.new)
klass.extend Garb::Model
klass.metrics << metrics(method_name)
klass.dimensions << dimensions(method_name)
return klass
end
The type of 'profile' that the Reporter expects is a Garb::Management::Profile.
In order to test some of my private methods on this Reporter class (such as valid_method_name? or build_class_name), I believe I want to mock the profile with rspec as it's not a detail that I'm interested in.
However, the call to klass.results(#profile) - is executing and killing me, so I haven't stubbed the Garb::Model that I'm extending in my meta part.
Here's how I'm mocking and stubbing so far... the spec implementation is of course not important:
describe GoogleAnalyticsReports::Reporter do
before do
#mock_model = mock('Garb::Model')
#mock_model.stub(:results) # doesn't work!
#mock_profile = mock('Garb::Management::Profile')
#mock_profile.stub!(:session)
#reporter = GoogleAnalyticsReports::Reporter.new(#mock_profile)
end
describe 'valid_method_name' do
it 'should not allow bla' do
#reporter.valid_method_name?('bla').should be_false
end
end
end
Does anyone know how I can stub the call to the results method on my newly created class?
Any pointers will be greatly appreciated!
~ Stu
Instead of:
#mock_model = mock('Garb::Model')
#mock_model.stub(:results) # doesn't work!
I think you want to do:
Garb::Model.any_instance.stub(:results)
This will stub out any instance of Garb::Model to return results. You need to do this because you are not actually passing #mock_model into any class/method that will use it so you have to be a bit more general.

Resources