Interface Mocking
UPDATE: This is a gem now: rspec-fire The code in the gem is better than that presented here.
Here is a screencast I put together in response to a recent Destroy All Software screencast on test isolation and refactoring, showing off an idea I’ve been tinkering around with for automatic validation of your implicit interfaces that you stub in tests.
Here is the code for InterfaceMocking:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
module InterfaceMocking # Returns a new interface double. This is equivalent to an RSpec double, # stub or, mock, except that if the class passed as the first parameter # is loaded it will raise if you try to set an expectation or stub on # a method that the class has not implemented. def interface_double(stubbed_class, methods = {}) InterfaceDouble.new(stubbed_class, methods) end module InterfaceDoubleMethods include RSpec::Matchers def should_receive(method_name) ensure_implemented(method_name) super end def should_not_receive(method_name) ensure_implemented(method_name) super end def stub!(method_name) ensure_implemented(method_name) super end def ensure_implemented(*method_names) if recursive_const_defined?(Object, @__stubbed_class__) recursive_const_get(Object, @__stubbed_class__). should implement(method_names, @__checked_methods__) end end def recursive_const_get object, name name.split('::').inject(Object) {|klass,name| klass.const_get name } end def recursive_const_defined? object, name !!name.split('::').inject(Object) {|klass,name| if klass && klass.const_defined?(name) klass.const_get name end } end end class InterfaceDouble < RSpec::Mocks::Mock include InterfaceDoubleMethods def initialize(stubbed_class, *args) args << {} unless Hash === args.last @__stubbed_class__ = stubbed_class @__checked_methods__ = :public_instance_methods ensure_implemented *args.last.keys # __declared_as copied from rspec/mocks definition of `double` args.last[:__declared_as] = 'InterfaceDouble' super(stubbed_class, *args) end end end RSpec::Matchers.define :implement do |expected_methods, checked_methods| match do |stubbed_class| unimplemented_methods( stubbed_class, expected_methods, checked_methods ).empty? end def unimplemented_methods(stubbed_class, expected_methods, checked_methods) implemented_methods = stubbed_class.send(checked_methods) unimplemented_methods = expected_methods - implemented_methods end failure_message_for_should do |stubbed_class| "%s does not publicly implement:\n%s" % [ stubbed_class, unimplemented_methods( stubbed_class, expected_methods, checked_methods ).sort.map {|x| " #{x}" }.join("\n") ] end end RSpec.configure do |config| config.include InterfaceMocking end |
Transactional before all with RSpec and DataMapper
By default, before(:all) in rspec executes outside of any transaction, meaning that you can’t really use it for creating objects. Normally this should go in a before(:each), but for a spec with simple creation and a large number of assertions this is terribly inefficient.
Let’s fix it!
This code assumes you are using DataMapper, and that your database supports some form of nested transactions (at the very least faking them with savepoints – see nested transactions in postgres with datamapper). It wraps each before/after :all and :each in it’s own transaction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
RSpec.configure do |config| [:all, :each].each do |x| config.before(x) do repository(:default) do |repository| transaction = DataMapper::Transaction.new(repository) transaction.begin repository.adapter.push_transaction(transaction) end end config.after(x) do repository(:default).adapter.pop_transaction.rollback end end config.include(RSpecExtensions::Set) end |
See that RSpecExtensions::Set include? That’s a version of the lovely let helpers that works with before(:all) setup. Props to pcreux for this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
module RSpecExtensions module Set module ClassMethods # Generates a method whose return value is memoized # in before(:all). Great for DB setup when combined with # transactional before alls. def set(name, &block) define_method(name) do __memoized[name] ||= instance_eval(&block) end before(:all) { __send__(name) } before(:each) do __send__(name).tap do |obj| obj.reload if obj.respond_to?(:reload) end end end end module InstanceMethods def __memoized # :nodoc: @__memoized ||= {} end end def self.included(mod) # :nodoc: mod.extend ClassMethods mod.__send__ :include, InstanceMethods end end end |
Fast specs make me a happy man.
Integration testing with Cucumber, RSpec and Thinking Sphinx
Ideally you would want to include sphinx in your integration tests. It’s really just like your database. In practice, this is problematic. Ensuring the DB is started and triggering a re-index after each model load is doable, if slow, with a small bit of hacking of thinking sphinx (hint – change the initializer for the ThinkingSphinx::Configuration to allow you to specify the environment). Here’s the rub though – if you’re using transactional fixtures the sphinx indexer won’t be able to see any of your data! Turning that off can really slow down your tests, and once you add in the re-indexing time you’re going to be making a few cups of coffee while they run.
One approach I’ve been taking is to stub out the search methods with RR. I know, I know, stubbing in your integration tests is evil. I’m being pragmatic here. For most applications your search is trivial (find me results for this keyword), and if you unit test your define_index block you’re pretty well covered. To go one step further you could unit test your controllers with an expect on the search method, or have a separate suite of non-transactional integration tests running against sphinx. I like the latter, but haven’t done it yet.
Enough talk! Here’s the magic you need to get it working with cucumber:
1 2 3 4 5 6 7 8 9 |
# features/steps/env.rb require 'rr' Cucumber::Rails::World.send(:include, RR::Adapters::RRMethods) # features/steps/*_steps.rb Given /a car with model '(\w+)' exists/ do |model| car = Car.create!(:model => model) stub(Car).search(model) { [car] } end |
Testing flash.now with RSpec
flash.now has always been a pain to test. The the traditional rails approach is to use assert_select and find it in your views. This clearly doesn’t work if you want to test your controller in isolation.
Other folks have found work arounds to the problem, including mocking out the flash or monkey patching it.
These solutions feel a bit like using a sledgehammer to me. If you’re going to monkey patch/mock something, you want it to be as discreet as possible so to minimize the chance of the implementation changing underneath you and also to reduce the affect on other areas of your application. Also, why duplicate perfectly good code that is provided elsewhere?
The real problem with testing flash.now is that it gets cleaned up (via #sweep) at the end of the action before you get to test anything. So let’s solve that problem and that problem only: disable sweeping of flash.now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# spec/spec_helper.rb module DisableFlashSweeping def sweep end end # A spec describe BogusController, "handling GET to #index" do it "sets flash.now[:message]" do @controller.instance_eval { flash.extend(DisableFlashSweeping) } get :index flash.now[:message].should_not be_nil end end |
instance_eval is used to access the flash, since it’s a protected method, and we extend with the minimum possible code to do what we want – blanking out the sweep method. This should not cause problems because sweeping is only relevant across multiple requests, which we shouldn’t be doing in our controller specs.