-
-
Save justinko/1511607 to your computer and use it in GitHub Desktop.
module ModuleStubbing | |
def stubbed_modules | |
@stubbed_modules ||= [] | |
end | |
def stub_module(full_name) | |
most_shallow_stubbed_module = nil | |
full_name.to_s.split(/::/).inject(Object) do |context, name| | |
begin | |
context.const_get(name) | |
rescue NameError | |
most_shallow_stubbed_module ||= [context, name] | |
context.const_set(name, Module.new) | |
end | |
end.tap do | |
if most_shallow_stubbed_module | |
stubbed_modules << most_shallow_stubbed_module | |
end | |
end | |
end | |
def cleanup_stub_modules | |
stubbed_modules.each do |(context, name)| | |
context.send(:remove_const, name) | |
end | |
end | |
end | |
include ModuleStubbing | |
RSpec.configure do |c| | |
c.after(:each) { cleanup_stub_modules } | |
end |
Interesting. I hadn't read anything about how this was used. I was just thinking it would be used like this:
describe MyClass
it 'delegates some logic to another class' do
foo_bar = stub_module("Foo::Bar")
foo_bar.should_receive(:bazz).and_return(17)
MyClass.bam.should eq(some_value_calculated_base_on_17)
end
end
...but that was just how I imagined it to be used.
I'm using it at the top level and in before blocks. The top level one is used for the initial describe
. The ones in the before
blocks are used to "reset" the modules that get wiped in the after
hook. I could probably change it to to use the "all" hooks instead.
stub_module('Bar') # Foo depends on Bar
require_relative '../../app/models/foo'
describe Foo do
before do
stub_module('Bar') # Since this gets removed in the `after` hook
end
end
Having to call stub_module('Bar')
twice is pretty nasty, but I can't think of another way.
One thought: this is only necessary if you use Bar
directly in Foo
's class body, right? If you only use Bar
in Foo
's methods, but not directly in the class body, you wouldn't need to make a stub module before requiring foo. Alternately, full isolation is nice, but if it's important for to use Bar
directly in the class body of Foo
maybe these things are very coupled and testing it in full isolation may not make sense.
A simple include Bar
in Foo
will require you to use stub_module('Bar')
in the top level. One way to avoid stub_module
is to include
it like so:
class Foo
include Bar if defined?(Bar)
end
But then you're adding test specific logic into the implementation code :(
maybe these things are very coupled and testing it in full isolation may not make sense
Not going to be an option. Currently, this suite takes 11 minutes to run. My goal is to have the capybara specs be the only "full stack" specs.
If you're spec'ing a model (
Foo
) that depends onBar
,Bar
needs to be available before you can call your top leveldescribe Foo do
.Why does the module need to be returned? It's using the
Object
"const" methods, so it will be available. However, doing things likestub_module('Foo').extend(MyModule)
would be nice so I'll addtap
back in.