diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d477dd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +rspec-fire-0.1.gem diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..a1b93f3 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source :rubygems + +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..8a528a7 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,26 @@ +PATH + remote: . + specs: + rspec-fire (0.1) + +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.3) + rake (0.9.2) + rspec (2.6.0) + rspec-core (~> 2.6.0) + rspec-expectations (~> 2.6.0) + rspec-mocks (~> 2.6.0) + rspec-core (2.6.4) + rspec-expectations (2.6.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.6.0) + +PLATFORMS + ruby + +DEPENDENCIES + rake + rspec (~> 2.5) + rspec-fire! diff --git a/HISTORY b/HISTORY new file mode 100644 index 0000000..cf721bc --- /dev/null +++ b/HISTORY @@ -0,0 +1,2 @@ +0.1 - 3 September 2011 + * Initial release diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..3fa0283 --- /dev/null +++ b/Rakefile @@ -0,0 +1,7 @@ +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) do |opts| + opts.rspec_opts = '--format documentation' +end + +desc 'Default: run specs.' +task :default => :spec diff --git a/lib/rspec/fire.rb b/lib/rspec/fire.rb new file mode 100644 index 0000000..4f42b72 --- /dev/null +++ b/lib/rspec/fire.rb @@ -0,0 +1,81 @@ +require 'rspec/mocks' + +module RSpec + module Fire + class FireDouble < RSpec::Mocks::Mock + def initialize(doubled_class, *args) + args << {} unless Hash === args.last + + @__doubled_class_name = doubled_class + @__checked_methods = :public_instance_methods + + # __declared_as copied from rspec/mocks definition of `double` + args.last[:__declared_as] = 'FireDouble' + super(doubled_class, *args) + end + + 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 + + protected + + def ensure_implemented(*method_names) + if recursive_const_defined?(Object, @__doubled_class_name) + recursive_const_get(Object, @__doubled_class_name). + 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 + + def implement(expected_methods, checked_methods) + RSpec::Matchers::Matcher.new(:implement, expected_methods, checked_methods) do |expected_methods, checked_methods| + unimplemented_methods = lambda {|doubled_class| + implemented_methods = doubled_class.send(checked_methods) + expected_methods - implemented_methods + } + + match do |doubled_class| + unimplemented_methods[ doubled_class ].empty? + end + + failure_message_for_should do |doubled_class| + implemented_methods = Object.public_methods - doubled_class.send(checked_methods) + "%s does not implement:\n%s" % [ + doubled_class, + unimplemented_methods[ doubled_class ].sort.map {|x| + " #{x}" + }.join("\n") + ] + end + end + end + end + + def fire_double(*args) + FireDouble.new(*args) + end + end +end diff --git a/rspec-fire.gemspec b/rspec-fire.gemspec new file mode 100644 index 0000000..40785e0 --- /dev/null +++ b/rspec-fire.gemspec @@ -0,0 +1,23 @@ +Gem::Specification.new do |s| + s.name = 'rspec-fire' + s.version = '0.1' + s.summary = 'More resilient test doubles for RSpec.' + s.platform = Gem::Platform::RUBY + s.authors = ["Xavier Shay"] + s.email = ["hello@xaviershay.com"] + s.homepage = "http://github.com/xaviershay/rspec-fire" + s.has_rdoc = false + + s.require_path = 'lib' + s.files = Dir.glob("{spec,lib}/**/*.rb") + + %w( + Gemfile + README.md + HISTORY + Rakefile + rspec-fire.gemspec + ) + + s.add_development_dependency 'rake' + s.add_development_dependency 'rspec', '~> 2.5' +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..c3e14d2 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,13 @@ +require 'rspec/fire' + +RSpec.configure do |config| + config.include(RSpec::Fire) + + def fail_matching(*messages) + raise_error(RSpec::Expectations::ExpectationNotMetError) {|error| + messages.all? {|message| + error.message =~ /#{Regexp.escape(message)}/ + } + } + end +end