How do I mock a Class with Ruby? - 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

Related

ruby / Passing arguments to a dynamically created class

There's probably many other better ways; but having the following piece of code :
class ApplicationService
def self.build(*args, &block)
new(*args, &block).build
end
end
class BaseClass; end
class Fetcher < ApplicationService
attr_reader :resource_name
def initialize(resource_name)
#resource_name = resource_name
end
def build
resource_name = #resource_name
Class.new(BaseClass) do
##resource_name = resource_name
class << self
def all
"http://some.remote.resource/#{##resource_name}/all"
end
end
end
end
end
in order to have the initial resource_name in the self.all method, i came up with defining ##resource_name = resource_name. I'm totally unsure if that's the good way to go.
I'd like to be able to use such 'generator', in order to provide the following interface :
## In some kind of initializers :
Xyz = Fetcher.build('xyz')
## Final use :
Xyz.all
Would there be a better pattern to have the class created dynamically, while passing arguments when creating this class ?
It is unclear why you want to create the class in the first place. If there are good reasons for it, my answer is kind of invalid.
You can have the desired behaviour using "standard" OOP techniques and working with instances
class Fetcher
def initialize(resource_name)
#resource_name = resource_name
end
def all
"http://some.remote.resource/#{#resource_name}/all"
end
end
xyz_fetcher = Fetcher.new('xyz')
xyz_fetcher.all
Otherwise, your code is more or less what you would/should do, I guess. Just, I would let the Fetcher class act as a singleton (not use an instance of Fetcher):
class Fetcher < ApplicationService
# make a singleton by privatizing initialize (read this up somewhere else)
def self.build(resource_name)
Class.new(BaseClass) do
##resource_name = resource_name
class << self
def all
"http://some.remote.resource/#{##resource_name}/all"
end
end
end
end
end
Then
Xyz = Fetcher.build('xyz')
Xyz.all
Now, you have the stuff with ApplicationService which more or less achieves that (and passes a block), so probably we readers miss some parts of the bigger picture ... please clarify if that is the case.
Besides singletonization, you could also work with modules instead (thanks #max for the comment).

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)

Stub and Mock Minitest

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

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.

Test to validate ruby subclasses implement strategy method

I'm implementing a simple strategy pattern (for the first time in ruby) and I want to write a test to make sure that every subclass implements the crucial strategy method. So, I have something like this:
class SearchTools::MusicSearcher
def find_artists
raise 'Abstract method called'
end
end
class SearchTools::LastFMSearcher < MusicSearcher
def find_artists(search_phrase)
# get artists from lastfm's restful api
end
end
class SearchTools::DatabaseSearcher < MusicSearcher
def find_artists(search_phrase)
# look in database for artists
end
end
class SearchTools::Search
def initialize(searcher)
#searcher = searcher
end
def find_artists(search_phrase)
#searcher.find_artists(search_phrase)
end
end
I'm currently using rspec, factory_girl and shoulda-matchers for my testing. Anyone know how I achieve this?
Cheers!
P.S. I'm used to specifying a literal 'interface' with C#, so that's why I'm looking to see what I can use in ruby to enforce a common interface for each strategy...
I would expect it to be something like,
it "should respond to find_artists method" do
o = SearchTools::LastFMSearcher.new
o.respond_to?(:find_artists).should be_true
end

Resources