Ruby - how to test method using minitest - ruby

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)

Related

Execute method in other context

How can I call Test#test method in DSL instance?
class DSL
def initialize(c)
x = # call c.test in self context
x == 'dsl_method_content_somethig' # => true
end
def dsl_method
'dsl_method_content'
end
end
class Test
def test
dsl_method + '_something'
end
end
DSL.new(Test.new)
Everything I tried gives me:
undefined local variable or method `dsl_method' for #<Test:0x007f8c0a934250> (NameError)
I think you might be trying to to include a module? If you just want dsl methods in one place, you can do this:
module DSL
def initialize
x = test
x == 'dsl_method_content_somethig' # => true
end
def dsl_method
'dsl_method_content'
end
end
Then in your class you can do:
class Test
include DSL
def test
dsl_method + '_something'
end
end
Although calling a method, that is defined in Test, from the DSL module is a little bit odd. As the module then needs to know implementation details, of every class it is included in.

error during wrapping the ruby code into a class

I am an RoR newbie. I have created a small application in ruby which has small functions to execute the code.
e.g.
def abc(xyz)
some code
end
def ghi(xyz)
some code
end
def jkl(output)
some code
end
xyz = abc[ARGV(0)]
output = ghi(xyz)
puts jkl(output)
Now, when I run this code in command prompt using ruby .rb, it executes nicely and returns the desired results. But when I try to create a class and add this whole code to it e.g.
class Foo
def abc(xyz)
some code
end
def ghi(xyz)
some code
end
def jkl(output)
some code
end
xyz = abc[ARGV(0)]
output = ghi(xyz)
puts jkl(output)
end
It generates the error like "undefined method 'abc' for Foo:Class (NoMethodError)"
All I want to ask is that how shall I add this code to a class so that it can become more pluggable and get the desired results.
Thanks in advance.
As this is written, these are all instance methods. You need to make them class methods like these two examples, or you could leave them as is and create an instance of the class. Either way, you should probably move the last three statements outside the class definition.
class Foo
class << self
def abc(xyz)
some code
end
def ghi(xyz)
some code
end
def jkl(output)
some code
end
end
end
xyz = Foo.abc('something')
output = Foo.ghi(xyz)
puts Foo.jkl(output)
OR....
class Foo
def self.abc(xyz)
some code
end
def self.ghi(xyz)
some code
end
def self.jkl(output)
some code
end
end
xyz = Foo.abc('something')
output = Foo.ghi(xyz)
puts Foo.jkl(output)
EDIT: To answer your question in the comments, this is how you would instantiate the class and call using instance methods.
class Foo
def abc(xyz)
some code
end
def ghi(xyz)
some code
end
def jkl(output)
some code
end
end
bar = Foo.new
xyz = bar.abc('something')
output = bar.ghi(xyz)
puts bar.jkl(output)
If you don't have any Ruby learning materials yet, you might want to check out Chris Pine's tutorial, which includes a section on classes and how they work. As for books, here is a great book for Ruby in general and here is a question regarding books for Rails. I would suggest getting a decent grasp of Ruby before getting too deep into Rails.

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.

How can I convert this code to meta-programming, so I can stop duplicating it?

I've got a small but growing framework for building .net systems with ruby / rake , that I've been working on for a while now. In this code base, I have the following:
require 'rake/tasklib'
def assemblyinfo(name=:assemblyinfo, *args, &block)
Albacore::AssemblyInfoTask.new(name, *args, &block)
end
module Albacore
class AssemblyInfoTask < Albacore::AlbacoreTask
def execute(name)
asm = AssemblyInfo.new
asm.load_config_by_task_name(name)
call_task_block(asm)
asm.write
fail if asm.failed
end
end
end
the pattern that this code follows is repeated about 20 times in the framework. The difference in each version is the name of the class being created/called (instead of AssemblyInfoTask, it may be MSBuildTask or NUnitTask), and the contents of the execute method. Each task has it's own execute method implementation.
I'm constantly fixing bugs in this pattern of code and I have to repeat the fix 20 times, every time I need a fix.
I know it's possible to do some meta-programming magic and wire up this code for each of my tasks from a single location... but I'm having a really hard time getting it to work.
my idea is that I want to be able to call something like this:
create_task :assemblyinfo do |name|
asm = AssemblyInfo.new
asm.load_config_by_task_name(name)
call_task_block(asm)
asm.write
fail if asm.failed
end
and this would wire up everything I need.
I need help! tips, suggestions, someone willing to tackle this... how can I keep from having to repeat this pattern of code over and over?
Update: You can get the full source code here: http://github.com/derickbailey/Albacore/ the provided code is /lib/rake/assemblyinfotask.rb
Ok, here's some metaprogramming that will do what you want (in ruby18 or ruby19)
def create_task(taskname, &execute_body)
taskclass = :"#{taskname}Task"
taskmethod = taskname.to_s.downcase.to_sym
# open up the metaclass for main
(class << self; self; end).class_eval do
# can't pass a default to a block parameter in ruby18
define_method(taskmethod) do |*args, &block|
# set default name if none given
args << taskmethod if args.empty?
Albacore.const_get(taskclass).new(*args, &block)
end
end
Albacore.const_set(taskclass, Class.new(Albacore::AlbacoreTask) do
define_method(:execute, &execute_body)
end)
end
create_task :AssemblyInfo do |name|
asm = AssemblyInfo.new
asm.load_config_by_task_name(name)
call_task_block(asm)
asm.write
fail if asm.failed
end
The key tools in the metaprogrammers tool box are:
class<<self;self;end - to get at the metaclass for any object, so you can define methods on that object
define_method - so you can define methods using current local variables
Also useful are
const_set, const_get: allow you to set/get constants
class_eval : allows you to define methods using def as if you were in a class <Classname> ... end region
Something like this, tested on ruby 1.8.6:
class String
def camelize
self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
end
end
class AlbacoreTask; end
def create_task(name, &block)
klass = Class.new AlbacoreTask
klass.send :define_method, :execute, &block
Object.const_set "#{name.to_s.camelize}Task", klass
end
create_task :test do |name|
puts "test: #{name}"
end
testing = TestTask.new
testing.execute 'me'
The core piece is the "create_task" method, it:
Creates new class
adds execute method
Names the class and exposes it

Resources