= CROSS-STUB makes cross process stubbing possible !! == Introduction Existing mocking/stubbing frameworks support only stubbing in the current process. This is OK most of the time. However, when running cucumber integration test suite in another process, these in-process stubbing frameworks simply doesn't help. Eg. I want Time.now to always return a timing that should be a Sunday, how do I do that when running cucumber using selenium, culerity, steam, blah, blah driver? It doesn't seem straight-forward me. (Let's not argue whether stubbing should be encouraged. It is an itch, the poor itch needs to be scratched.) == Getting Started It's hosted on gemcutter.org. $ gem install cross-stub == Setting Up === 1. Rails-3.* $ rails g cucumber:install $ rails g cross_stub === 2. Rails-2.* $ ./script/generate cucumber $ ./script/generate cross_stub === 3. Others (back to basics) # In the test setup method: CrossStub.setup :file => # In the test teardown method: CrossStub.clear # Find an entry point in your target application, eg. in a server, the # point where all request handling starts: CrossStub.refresh :file => For a full list of available cache stores, scroll down to take a look at the 'Cache Stores' section. == Using It Cross-stubbing is simple: === 1. Simple returning of nil or non-nil value: ==== 1.1. Class method: class Someone def self.say 'hello' end end Someone.xstub(:say) Someone.say # yields: nil Someone.xstub(:say => 'HELLO') Someone.say # yields: 'HELLO' ==== 1.2. Instance method: Someone.xstub(:say, :instance => true) Someone.new.say # yields: nil Someone.xstub({:say => 'HELLO'}, :instance => true) Someone.new.say # yields: 'HELLO' === 2. If a stubbed method requires argument, pass xstub a proc: ==== 2.1. Class method: Someone.xstub do def say(something) 'saying "%s"' % something end end Someone.say('HELLO') # yields: 'saying "HELLO"' ==== 2.2. Instance method: Someone.xstub(:instance => true) do def say(something) 'saying "%s"' % something end end Someone.new.say('HELLO') # yields: 'saying "HELLO"' === 3. Something more complicated: something = 'hello' Someone.xstub do def say 'saying "%s"' % something end end Someone.say # failure !! The above fails as a result of undefined variable/method 'something', to workaround we can have: Someone.xstub(:something => 'HELLO') do def say 'saying "%s"' % something end end Someone.say # yields: 'saying "HELLO"' == Cache Stores Cache stores are needed to allow stubs to be made available for different processes. The following describes all cache stores available: === 1. File # Setting up (current process) CrossStub.setup :file => '' # Refreshing (other process) CrossStub.refresh :file => '' === 2. Memcache (requires memcache-client gem) # Setting up (current process) CrossStub.setup :memcache => 'localhost:11211/' # Refreshing (other process) CrossStub.refresh :memcache => 'localhost:11211/' === 3. Redis (requires redis gem) # Setting up (current process) CrossStub.setup :redis => 'localhost:6379/' # Refreshing (other process) CrossStub.refresh :redis => 'localhost:6379/' Adding new store is super easy (w.r.t testing & actual implementation), let me know if u need more :] == Caveats 1. CrossStub uses ruby's Marshal class to dump & load the stubs, thus it has the same limitations as Marshal (pls note abt a 1.9.1 specific marshal bug at http://redmine.ruby-lang.org/issues/show/3729) 2. Under the hood, CrossStub uses Sourcify (http://github.com/ngty/sourcify) for extracting the methods defined within the proc, u may wish to read Sourcify's gotchas to avoid unnecessary headaches. == TODO(s) 1. Is there any better serialization strategy instead of the current Marshal load/dump? == Contacts Written since 2009 by: 1. NgTzeYang, contact ngty77[at]gmail[dot]com or http://github.com/ngty 2. WongLiangZan, contact liangzan[at]gmail[dot]com or http://github.com/liangzan Released under the MIT license