I've got code that only needs to run on a certain version of ActiveRecord (a workaround for a bug on old AR libraries). This code tests the values of ActiveRecord::VERSION constants to see if it needs to be run.
Is there a way to mock out those constants in rspec so I can test that code path without relying on having the right ActiveRecord gem installed on the test machine?
I ended up writing a helper method to let me override constants while executing a block of code:
def with_constants(constants, &block)
constants.each do |constant, val|
Object.const_set(constant, val)
end
block.call
constants.each do |constant, val|
Object.send(:remove_const, constant)
end
end
After putting this code in your spec_helper.rb file, it can be used as follows:
with_constants :RAILS_ROOT => "bar", :RAILS_ENV => "test" do
code goes here ...
end
Hope this works for you.
With RSpec 2.11, constant stubbing is supported out of the box with stub_const:
describe "stub_const" do
it "changes the constant value for the duration of the example" do
stub_const("Foo::SIZE", 10)
expect(Foo::SIZE).to eq(10)
end
end
See Myron Marston's announcement for more details:
http://myronmars.to/n/dev-blog/2012/06/constant-stubbing-in-rspec-2-11
Drew Olson, I took your idea and made a few modifications to add scoping:
class Object
def self.with_constants(constants, &block)
old_constants = Hash.new
constants.each do |constant, val|
old_constants[constant] = const_get(constant)
silence_stderr{ const_set(constant, val) }
end
block.call
old_constants.each do |constant, val|
silence_stderr{ const_set(constant, val) }
end
end
end
After putting this code at specs/support/with_constants.rb file, it can be used as follows:
MyModel.with_constants :MAX_RESULT => 2, :MIN_RESULT => 1 do
code goes here ...
end
Add rescue block is important for ensure restore constant for another tests in test suite !
class Object
class << self
def with_constants(constants, &block)
old_constants = Hash.new
constants.each do |constant, val|
old_constants[constant] = const_get(constant)
Kernel::silence_warnings { const_set(constant, val) }
end
error = nil
begin
block.call
rescue Exception => e
error = e
end
old_constants.each do |constant, val|
Kernel::silence_warnings { const_set(constant, val) }
end
raise error unless error.nil?
end
end
end
Typically
describe "#fail" do
it "should throw error" do
expect {
MyModel.with_constants(:MAX_RESULT => 1) do
# code with throw error
end
}.to raise_error
end
end
Related
I am building a DSL and have this module
module EDAApiBuilder
module Client
attr_accessor :api_client, :endpoint, :url
def api_client(api_name)
#apis ||= {}
raise ArgumentError.new('API name already exists.') if #apis.has_key?(api_name)
#api_client = api_name
#apis[#api_client] = {}
yield(self) if block_given?
end
def fetch_client(api_name)
#apis[api_name]
end
def endpoint(endpoint_name)
raise ArgumentError.new("Endpoint #{endpoint_name} already exists for #{#api_client} API client.") if fetch_client(#api_client).has_key?(endpoint_name)
#endpoint = endpoint_name
#apis[#api_client][#endpoint] = {}
yield(self) if block_given?
end
def url=(endpoint_url)
fetch_client(#api_client)[#endpoint]['url'] = endpoint_url
end
end
end
so that I have tests like
context 'errors' do
it 'raises an ArgumentError when trying to create an already existent API client' do
expect {
obj = MixinTester.new
obj.api_client('google')
obj.api_client('google')
}.to raise_error(ArgumentError,'API name already exists.')
end
it 'raises an ArgumentError when trying to create a repeated endpoint for the same API client' do
expect {
obj = MixinTester.new
obj.api_client('google') do |apic|
apic.endpoint('test1')
apic.endpoint('test1')
end
}.to raise_error(ArgumentError,"Endpoint test1 already exists for google API client.")
end
end
I would rather have #api_clientwritten as an assignment block
def api_client=(api_name)
so that I could write
obj = MixinTester.new
obj.api_client = 'google' do |apic| # <=== Notice the difference here
apic.endpoint('test1')
apic.endpoint('test1')
end
because I think this notation (with assignment) is more meaningful. But then, when I run my tests this way I just get an error saying that the keyworkd_do is unexpected in this case.
It seems to me that the definition of an assignment block is syntactic sugar which won't contemplate blocks.
Is this correct? Does anyone have some information about this?
By the way: MixinTester is just a class for testing, defined in my spec/spec_helper.rb as
class MixinTester
include EDAApiBuilder::Client
end
SyntaxError
It seems to me that the definition of an assignment [method] is syntactic
sugar which won't contemplate blocks.
It seems you're right. It looks like no method with = can accept a block, even with the normal method call and no syntactic sugar :
class MixinTester
def name=(name,&block)
end
def set_name(name, &block)
end
end
obj = MixinTester.new
obj.set_name('test') do |x|
puts x
end
obj.name=('test') do |x| # <- syntax error, unexpected keyword_do, expecting end-of-input
puts x
end
Alternative
Hash parameter
An alternative could be written with a Hash :
class MixinTester
def api(params, &block)
block.call(params)
end
end
obj = MixinTester.new
obj.api client: 'google' do |apic|
puts apic
end
#=> {:client=>"google"}
You could adjust the method name and hash parameters to taste.
Parameter with block
If the block belongs to the method parameter, and not the setter method, the syntax is accepted :
def google(&block)
puts "Instantiate Google API"
block.call("custom apic object")
end
class MixinTester
attr_writer :api_client
end
obj = MixinTester.new
obj.api_client = google do |apic|
puts apic
end
# =>
# Instantiate Google API
# custom apic object
It looks weird, but it's pretty close to what you wanted to achieve.
I have next scenario:
module Module
class CommandPattern
def initialize(value)
command = []
#var = value['something']
#abc = value['abc']
#command << value
end
def add(value)
#command << value
end
def get_command
#command
end
end
end
module Module
class Implementator
def initialize(value)
#value = value
end
def method_to_test(argument)
var = "command1"
cmd = CommandPattern.new(var)
var2 = "command2"
cmd.add(var2)
var3 = argument
cmd.add(var3)
commands = var + var2 + var3
commands
end
end
end
So, when I'm testing Module::B.method_I_want_to_test, what would be the best practice to mock "var = A.new(some_stuff)"? Beside refactoring and moving this line into separate method, is there some nice way to do this?
Little bit of background on this question - this style (Module::ClassA and Module::ClassB) - I'm using http://naildrivin5.com/gli/ and reason for this approach is that class A is actually implementing Command Pattern.
So issue I was apparently getting was due to wrong way of trying to write specs.
What I did before was (on the way how #spickermann advised):
RSpec.describe Module::Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
Issue was apparently in testing Module::Implementator, didn't realise I can put module around my RSpec.describe block and solve my first issue:
module Module
RSpec.describe Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
end
Another issue I had was global variable keeping YAML structure, which I missed to see and declare in spec_helper.rb
However, thank's to #spickermann's advices, issue is solved.
I would start with something like this:
describe '#method_I_want_to_test' do
let(:something) { # whatever something needs to be }
let(:a) { double(A, # methods you need from a) }
subject(:method_I_want_to_test) do
B.new(something).method_I_want_to_test
end
before do
allow(A).to receive(:new).with(something).and_return(a)
end
it 'does what I expect' do
expect(method_I_want_to_test).to eq(# what do_semething_else returns)
end
end
The interesting part is the before block that stubs the new method on A. It returns always the double defined in the let(:a) line instead of a real instance of A
I was looking in detail at the Thread class. Basically, I was looking for an elegant mechanism to allow thread-local variables to be inherited as threads are created. For example the functionality I am looking to create would ensure that
Thread.new do
self[:foo]="bar"
t1=Thread.new { puts self[:foo] }
end
=> "bar"
i.e. a Thread would inherit it's calling thread's thread-local variables
So I hit upon the idea of redefining Thread.new, so that I could add an extra step to copy the thread-local variables into the new thread from the current thread. Something like this:
class Thread
def self.another_new(*args)
o=allocate
o.send(:initialize, *args)
Thread.current.keys.each{ |k| o[k]=Thread.current[k] }
o
end
end
But when I try this I get the following error:
:in `allocate': allocator undefined for Thread (TypeError)
I thought that as Thread is a subclass of Object, it should have a working #allocate method. Is this not the case?
Does anyone have any deep insight on this, and on how to achieve the functionality I am looking for.
Thanks in advance
Steve
Thread.new do
Thread.current[:foo]="bar"
t1=Thread.new(Thread.current) do |parent|
puts parent[:foo] ? parent[:foo] : 'nothing'
end.join
end.join
#=> bar
UPDATED:
Try this in irb:
thread_ext.rb
class Thread
def self.another_new(*args)
parent = Thread.current
a = Thread.new(parent) do |parent|
parent.keys.each{ |k| Thread.current[k] = parent[k] }
yield
end
a
end
end
use_case.rb
A = Thread.new do
Thread.current[:local_a]="A"
B1 =Thread.another_new do
C1 = Thread.another_new{p Thread.current[:local_a] }.join
end
B2 =Thread.another_new do
C2 = Thread.another_new{p Thread.current[:local_a] }.join
end
[B1, B2].each{|b| b.join }
end.join
output
"A"
"A"
Here is a revised answer based on #CodeGroover's suggestion, with a simple unit test harness
ext/thread.rb
class Thread
def self.inherit(*args, &block)
parent = Thread.current
t = Thread.new(parent, *args) do |parent|
parent.keys.each{ |k| Thread.current[k] = parent[k] }
yield *args
end
t
end
end
test/thread.rb
require 'test/unit'
require 'ext/thread'
class ThreadTest < Test::Unit::TestCase
def test_inherit
Thread.current[:foo]=1
m=Mutex.new
#check basic inheritence
t1= Thread.inherit do
assert_equal(1, Thread.current[:foo])
end
#check inheritence with parameters - in this case a mutex
t2= Thread.inherit(m) do |m|
assert_not_nil(m)
m.synchronize{ Thread.current[:bar]=2 }
assert_equal(1, Thread.current[:foo])
assert_equal(2, Thread.current[:bar])
sleep 0.1
end
#ensure t2 runs its mutexs-synchronized block first
sleep 0.05
#check that the inheritence works downwards only - not back up in reverse
m.synchronize do
assert_nil(Thread.current[:bar])
end
[t1,t2].each{|x| x.join }
end
end
I was looking for the same thing recently and was able to come up with the following answer. Note I am aware the following is a hack and not recommended, but for the sake of answering the specific question on how you could alter the Thread.new functionality, I have done as following:
class Thread
class << self
alias :original_new :new
def new(*args, **options, &block)
original_thread = Thread.current
instance = original_new(*args, **options, &block)
original_thread.keys.each do |key|
instance[key] = original_thread[key]
end
instance
end
end
end
Heres what I have/want:
module Observable
def observers; #observers; end
def trigger(event, *args)
good = true
return good unless (#observers ||= {})[event]
#obersvers[event].each { |e| good = false and break unless e.call(self, args) }
good
end
def on(event, &block)
#obersvers ||= {}
#obersvers[event] ||= []
#observers[event] << block
end
end
class Item < Thing
include Observable
def pickup(pickuper)
return unless trigger(:before_pick_up, pickuper)
pickuper.add_to_pocket self
trigger(:after_pick_up, pickuper)
end
def drop(droper)
return unless trigger(:before_drop, droper)
droper.remove_from_pocket self
trigger(:after_drop, droper)
end
# Lots of other methods
end
# How it all should work
Item.new.on(:before_pickup) do |item, pickuper|
puts "Hey #{pickuper} thats my #{item}"
return false # The pickuper never picks up the object
end
While starting on trying to create a game in Ruby, I thought it would be great if it could be based all around Observers and Events. The problem is have to write all of these triggers seems to be a waste, as it seems like a lot of duplicated code. I feel there must be some meta programming method out there to wrap methods with functionality.
Ideal Sceanrio:
class CustomBaseObject
class << self
### Replace with correct meta magic
def public_method_called(name, *args, &block)
return unless trigger(:before_+name.to_sym, args)
yield block
trigger(:after_+name.to_sym, args)
end
###
end
end
And then I have all of my object inherit from this Class.
I'm still new to Ruby's more advanced meta programming subjects, so any knowledge about this type of thing would be awesome.
There are a several ways to do it with the help of metaprogramming magic. For example, you can define a method like this:
def override_public_methods(c)
c.instance_methods(false).each do |m|
m = m.to_sym
c.class_eval %Q{
alias #{m}_original #{m}
def #{m}(*args, &block)
puts "Foo"
result = #{m}_original(*args, &block)
puts "Bar"
result
end
}
end
end
class CustomBaseObject
def test(a, &block)
puts "Test: #{a}"
yield
end
end
override_public_methods(CustomBaseObject)
foo = CustomBaseObject.new
foo.test(2) { puts 'Block!' }
# => Foo
Test: 2
Block!
Bar
In this case, you figure out all the required methods defined in the class by using instance_methods and then override them.
Another way is to use so-called 'hook' methods:
module Overrideable
def self.included(c)
c.instance_methods(false).each do |m|
m = m.to_sym
c.class_eval %Q{
alias #{m}_original #{m}
def #{m}(*args, &block)
puts "Foo"
result = #{m}_original(*args, &block)
puts "Bar"
result
end
}
end
end
end
class CustomBaseObject
def test(a, &block)
puts "Test: #{a}"
yield
end
include Overrideable
end
The included hook, defined in this module, is called when you include that module. This requires that you include the module at the end of the class definition, because included should know about all the already defined methods. I think it's rather ugly :)
My first thoughts are some thing like this:
class AbstractBuilder
attr_reader :time_taken
def build_with_timer
started_at = Time.now
build
#time_taken = Time.now - started_at
end
def build
raise 'Implement this method in a subclass'
end
end
class MyBuilder < AbstractBuilder
def build
sleep(5)
end
end
builder = MyBuilder.new.build_with_timer
puts builder.time_taken
I would suspect there is a better way which offers better flexibility, for example ideally I'd like to call 'build' on an instance of MyBuilder instead of 'build_with_timer' and always have the execution time recorded.
I did consider using alias_method from initialize or even using a module mixin instead of class inheritance which would override the build method calling super in the middle (not sure if that would work). Before I go down the rabbit hole I thought I'd see if there is an established practice.
I had a stab at a version to achieve what you want. This version doesn't require the subclass to have any extra code either.
class AbstractBuilder
##disable_override = false
def before_method
puts "before"
end
def after_method
puts "after"
end
def self.method_added name
unless ##disable_override
if name == :build
##disable_override = true # to stop the new build method
self.send :alias_method, :sub_build, :build
self.send :remove_method, :build
self.send :define_method, :build do
before_method
sub_build
after_method
end
##disable_override = false
else
puts "defining other method #{name}"
end
end
end
end
class MyBuilder < AbstractBuilder
def build
puts "starting build"
sleep(5)
puts "built."
end
def unnaffected_method
# this method won't get redefined
end
end
b = MyBuilder.new
b.build
Outputs
defining other method unnaffected_method
before
starting build
built.
after
I'd play with alias_method:
module Timeable
def time_methods *meths
meths.each do |meth|
alias_method "old_#{meth}", meth
define_method meth do |*args|
started_at = Time.now
res = send "old_#{meth}", *args
puts "Execution took %f seconds" % (Time.now - started_at)
res
end
end
end
end
class Foo
def bar str
puts str
end
end
Foo.extend Timeable
Foo.time_methods :bar
Foo.new.bar('asd')
#=>asd
#=>Execution took 0.000050 seconds
Sounds like you're looking for hooks into object lifecycle events. You'll have to build this into your base object and provide a little DSL -- I'm thinking you're after something like ActiveRecord Callbacks. Here's how we might modify your example to allow something like that:
class AbstractBuilder
attr_reader :time_taken
def construct! # i.e., build, and also call your hooks
##prebuild.each { |sym| self.send(sym) }
build
##postbuild.each { |sym| self.send(sym) }
end
def construct_with_timer
started_at = Time.now
construct!
#time_taken = Time.now - started_at
puts "!!! Build time: ##time_taken"
end
class << self
def before_build(fn); ##prebuild ||= []; ##prebuild << fn; end
def after_build(fn); ##postbuild ||= []; ##postbuild << fn; end
end
end
class MyBuilder < AbstractBuilder
before_build :preprocess
after_build :postprocess
def build; puts "BUILDING"; sleep(3); end
def preprocess; puts "Preparing to build..."; end
def postprocess; puts "Done building. Thank you for waiting."; end
end
builder = MyBuilder.new
builder.construct_with_timer
# => Preparing to build...
# => BUILDING
# => Done building. Thank you for waiting.
# => !!! Build time: 3.000119
This is a textbook-definition use case for Aspect-Oriented Programming. It generally offers a cleaner separation of concerns. In this arena, Ruby offers Aquarium and AspectR. However, you may not want to add another dependency to your project. As such, you might still consider using one of the other approaches.