spy-0.4.3/0000755000004100000410000000000012706267032012366 5ustar www-datawww-dataspy-0.4.3/Rakefile0000644000004100000410000000030112706267032014025 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'rake/testtask' Rake::TestTask.new do |t| t.libs << "test" t.test_files = FileList['test/**/test*.rb'] t.verbose = true end task :default => [:test] spy-0.4.3/Gemfile0000644000004100000410000000022112706267032013654 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in spies.gemspec gemspec gem 'yard' gem 'redcarpet', platforms: :mri gem 'rake' spy-0.4.3/LICENSE.txt0000644000004100000410000000205112706267032014207 0ustar www-datawww-dataCopyright (c) 2013 Ryan Ong MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. spy-0.4.3/spec/0000755000004100000410000000000012706267032013320 5ustar www-datawww-dataspy-0.4.3/spec/spec_helper.rb0000644000004100000410000000162312706267032016140 0ustar www-datawww-datarequire "rspec/core" require "rspec/matchers" require "rspec/expectations" require "spy" require "pry" require "pry-nav" RSpec::Matchers.define :include_method do |expected| match do |actual| actual.map { |m| m.to_s }.include?(expected.to_s) end end RSpec.configure do |config| config.color_enabled = true config.order = :random config.run_all_when_everything_filtered = true config.treat_symbols_as_metadata_keys_with_true_values = true config.filter_run_including :focus config.filter_run_excluding :broken => true config.mock_with :absolutely_nothing config.expect_with :rspec do |expectations| expectations.syntax = :expect end old_verbose = nil config.before(:each, :silence_warnings) do old_verbose = $VERBOSE $VERBOSE = nil end config.after(:each, :silence_warnings) do $VERBOSE = old_verbose end config.after(:each) do Spy.teardown end end spy-0.4.3/spec/spy/0000755000004100000410000000000012706267032014133 5ustar www-datawww-dataspy-0.4.3/spec/spy/and_call_original_spec.rb0000644000004100000410000001161412706267032021116 0ustar www-datawww-datarequire 'spec_helper' describe "and_call_through" do context "on a partial mock object" do let(:klass) do Class.new do def meth_1 :original end def meth_2(x) yield x, :additional_yielded_arg end def self.new_instance new end end end let(:instance) { klass.new } it 'passes the received message through to the original method' do spy = Spy.on(instance, :meth_1).and_call_through expect(instance.meth_1).to eq(:original) expect(spy).to have_been_called end it 'passes args and blocks through to the original method' do spy = Spy.on(instance, :meth_2).and_call_through value = instance.meth_2(:submitted_arg) { |a, b| [a, b] } expect(value).to eq([:submitted_arg, :additional_yielded_arg]) expect(spy).to have_been_called end it 'works for singleton methods' do def instance.foo; :bar; end spy = Spy.on(instance, :foo).and_call_through expect(instance.foo).to eq(:bar) expect(spy).to have_been_called end it 'works for methods added through an extended module' do instance.extend Module.new { def foo; :bar; end } spy = Spy.on(instance, :foo).and_call_through expect(instance.foo).to eq(:bar) expect(spy).to have_been_called end it "works for method added through an extended module onto a class's ancestor" do sub_sub_klass = Class.new(Class.new(klass)) klass.extend Module.new { def foo; :bar; end } spy = Spy.on(sub_sub_klass, :foo).and_call_through expect(sub_sub_klass.foo).to eq(:bar) expect(spy).to have_been_called end it "finds the method on the most direct ancestor even if the method " + "is available on more distant ancestors" do klass.extend Module.new { def foo; :klass_bar; end } sub_klass = Class.new(klass) sub_klass.extend Module.new { def foo; :sub_klass_bar; end } spy = Spy.on(sub_klass, :foo).and_call_through expect(sub_klass.foo).to eq(:sub_klass_bar) expect(spy).to have_been_called end it 'works for class methods defined on a superclass' do subclass = Class.new(klass) spy = Spy.on(subclass, :new_instance).and_call_through expect(subclass.new_instance).to be_a(subclass) expect(spy).to have_been_called end it 'works for class methods defined on a grandparent class' do sub_subclass = Class.new(Class.new(klass)) spy = Spy.on(sub_subclass, :new_instance).and_call_through expect(sub_subclass.new_instance).to be_a(sub_subclass) expect(spy).to have_been_called end it 'works for class methods defined on the Class class' do spy = Spy.on(klass, :new).and_call_through expect(klass.new).to be_an_instance_of(klass) expect(spy).to have_been_called end it "works for instance methods defined on the object's class's superclass" do subclass = Class.new(klass) inst = subclass.new spy = Spy.on(inst, :meth_1).and_call_through expect(inst.meth_1).to eq(:original) expect(spy).to have_been_called end it 'works for aliased methods' do klass = Class.new do class << self alias alternate_new new end end spy = Spy.on(klass, :alternate_new).and_call_through expect(klass.alternate_new).to be_an_instance_of(klass) expect(spy).to have_been_called end context 'on an object that defines method_missing' do before do klass.class_eval do def respond_to_missing?(name, _) if name.to_s == "greet_jack" true else super end end def method_missing(name, *args) if name.to_s == "greet_jack" "Hello, jack" else super end end end end it 'works when the method_missing definition handles the message' do spy = Spy.on(instance, :greet_jack).and_call_through expect(instance.greet_jack).to eq("Hello, jack") expect(spy).to have_been_called end it 'raises an error on invocation if method_missing does not handle the message' do Spy::Subroutine.new(instance, :not_a_handled_message).hook(force: true).and_call_through # Note: it should raise a NoMethodError (and usually does), but # due to a weird rspec-expectations issue (see #183) it sometimes # raises a `NameError` when a `be_xxx` predicate matcher has been # recently used. `NameError` is the superclass of `NoMethodError` # so this example will pass regardless. # If/when we solve the rspec-expectations issue, this can (and should) # be changed to `NoMethodError`. expect { instance.not_a_handled_message }.to raise_error(NameError, /not_a_handled_message/) end end end end spy-0.4.3/spec/spy/serialization_spec.rb0000644000004100000410000000637312706267032020360 0ustar www-datawww-datarequire 'spec_helper' require 'yaml' require 'psych' require 'syck' module Spy describe "serialization" do class SerializableObject < Struct.new(:foo, :bar); end class SerializableMockProxy attr_reader :mock_proxy def initialize(mock_proxy) @mock_proxy = mock_proxy end def ==(other) other.class == self.class && other.mock_proxy == mock_proxy end end def self.with_yaml_loaded(&block) context 'with YAML loaded' do module_eval(&block) end end def self.without_yaml_loaded(&block) context 'without YAML loaded' do before do # We can't really unload yaml, but we can fake it here... @yaml = YAML Object.send(:remove_const, "YAML") Struct.class_eval do alias __old_to_yaml to_yaml undef to_yaml end end module_eval(&block) after do Struct.class_eval do alias to_yaml __old_to_yaml undef __old_to_yaml end Object.send(:const_set, "YAML", @yaml) end end end let(:serializable_object) { SerializableObject.new(7, "something") } def set_spy Spy::Subroutine.new(serializable_object, :bazz).hook(force: true).and_return(5) end shared_examples_for 'normal YAML serialization' do it 'serializes to yaml the same with and without stubbing, using #to_yaml' do set_spy expect { Spy.get(serializable_object, :bazz) }.to_not change { serializable_object.to_yaml } end it 'serializes to yaml the same with and without stubbing, using YAML.dump' do set_spy expect { Spy.get(serializable_object, :bazz) }.to_not change { ::YAML.dump(serializable_object) } end end with_yaml_loaded do compiled_with_psych = begin require 'psych' true rescue LoadError false end if compiled_with_psych context 'using Syck as the YAML engine' do before(:each) { ::YAML::ENGINE.yamler = 'syck' } it_behaves_like 'normal YAML serialization' end context 'using Psych as the YAML engine' do before(:each) { ::YAML::ENGINE.yamler = 'psych' } it_behaves_like 'normal YAML serialization' end else it_behaves_like 'normal YAML serialization' end end without_yaml_loaded do it 'does not add #to_yaml to the stubbed object' do expect(serializable_object).not_to respond_to(:to_yaml) set_spy expect(serializable_object).not_to respond_to(:to_yaml) end end it 'marshals the same with and without stubbing' do expect { Spy.get(serializable_object, :bazz) }.to_not change { Marshal.dump(serializable_object) } end describe "an object that has its own mock_proxy instance variable" do let(:serializable_object) { SerializableMockProxy.new(:my_mock_proxy) } it 'does not interfere with its marshalling' do marshalled_copy = Marshal.load(Marshal.dump(serializable_object)) expect(marshalled_copy).to eq serializable_object end end end end spy-0.4.3/spec/spy/to_ary_spec.rb0000644000004100000410000000177612706267032017002 0ustar www-datawww-datarequire "spec_helper" describe "a double receiving to_ary" do shared_examples "to_ary" do it "returns nil" do expect do expect(obj.to_ary).to be_nil end.to raise_error(NoMethodError) end it "doesn't respond" do expect(obj).not_to respond_to(:to_ary) end it "can be overridden with a stub" do Spy::Subroutine.new(obj, :to_ary).hook(force: true).and_return(:non_nil_value) expect(obj.to_ary).to be(:non_nil_value) end it "responds when overriden" do Spy::Subroutine.new(obj, :to_ary).hook(force: true).and_return(:non_nil_value) expect(obj).to respond_to(:to_ary) end it "supports Array#flatten" do obj = Spy.double('foo') expect([obj].flatten).to eq([obj]) end end context "double as_null_object" do let(:obj) { Spy.double('obj').as_null_object } include_examples "to_ary" end context "double without as_null_object" do let(:obj) { Spy.double('obj') } include_examples "to_ary" end end spy-0.4.3/spec/spy/nil_expectation_warning_spec.rb0000644000004100000410000000326112706267032022406 0ustar www-datawww-datarequire 'spec_helper' def remove_last_describe_from_world RSpec::world.example_groups.pop end def empty_example_group RSpec::Core::ExampleGroup.describe(Object, 'Empty Behaviour Group') { } remove_last_describe_from_world end module RSpec module Mocks describe "an expectation set on nil" do it "issues a warning with file and line number information" do expected_warning = %r%An expectation of :foo was set on nil. Called from #{__FILE__}:#{__LINE__+3}(:in .+)?. Use allow_message_expectations_on_nil to disable warnings.% Kernel.should_receive(:warn).with(expected_warning) nil.should_receive(:foo) nil.foo end it "issues a warning when the expectation is negative" do Kernel.should_receive(:warn) nil.should_not_receive(:foo) end it "does not issue a warning when expectations are set to be allowed" do allow_message_expectations_on_nil Kernel.should_not_receive(:warn) nil.should_receive(:foo) nil.should_not_receive(:bar) nil.foo end end describe "#allow_message_expectations_on_nil" do it "does not effect subsequent examples" do example_group = empty_example_group example_group.it("when called in one example that doesn't end up setting an expectation on nil") do allow_message_expectations_on_nil end example_group.it("should not effect the next exapmle ran") do Kernel.should_receive(:warn) nil.should_receive(:foo) nil.foo end example_group end end end end spy-0.4.3/spec/spy/stub_implementation_spec.rb0000644000004100000410000000326012706267032021555 0ustar www-datawww-datarequire 'spec_helper' module Spy describe Subroutine do class Bar def foo(given = nil) end end let(:obj) { Bar.new } describe "stub implementation" do describe "with no args" do it "execs the block when called" do Subroutine.new(obj, :foo).hook.and_return { :bar } expect(obj.foo).to eq :bar end end describe "with one arg" do it "execs the block with that arg when called" do Subroutine.new(obj, :foo).hook.and_return {|given| given} expect(obj.foo(:bar)).to eq :bar end end describe "with variable args" do it "execs the block when called" do Subroutine.new(obj, :foo).hook.and_return {|*given| given.first} expect(obj.foo(:bar)).to eq :bar end end end describe "unstub implementation" do it "replaces the stubbed method with the original method" do obj = Object.new def obj.foo; :original; end Subroutine.new(obj, :foo).hook.unhook expect(obj.foo).to eq :original end it "restores the correct implementations when stubbed and unstubbed on a parent and child class" do parent = Class.new child = Class.new(parent) Spy.on(parent, :new) Spy.on(child, :new) Spy.off(parent, :new) Spy.off(child, :new) expect(parent.new).to be_an_instance_of parent expect(child.new).to be_an_instance_of child end it "raises a MockExpectationError if the method has not been stubbed" do obj = Object.new expect { Spy.off(obj, :foo) }.to raise_error end end end end spy-0.4.3/spec/spy/stash_spec.rb0000644000004100000410000000213512706267032016615 0ustar www-datawww-datarequire 'spec_helper' module Spy describe "only stashing the original method" do let(:klass) do Class.new do def self.foo(arg) :original_value end end end it "keeps the original method intact after multiple expectations are added on the same method" do spy = Spy.on(klass, :foo) klass.foo(:bazbar) expect(spy).to have_been_called Spy.off(klass, :foo) expect(klass.foo(:yeah)).to equal(:original_value) end end describe "when a class method is aliased on a subclass and the method is mocked" do let(:klass) do Class.new do class << self alias alternate_new new end end end it "restores the original aliased public method" do klass = Class.new do class << self alias alternate_new new end end spy = Spy.on(klass, :alternate_new) expect(klass.alternate_new).to be_nil expect(spy).to have_been_called Spy.off(klass, :alternate_new) expect(klass.alternate_new).to be_an_instance_of(klass) end end end spy-0.4.3/spec/spy/passing_argument_matchers_spec.rb0000644000004100000410000001065312706267032022733 0ustar www-datawww-datarequire 'spec_helper' module Spy describe "Matchers", :broken do before(:each) do @double = double('double') Spy.on(Kernel, :warn) end after(:each) do @double.rspec_verify end context "handling argument matchers" do it "accepts true as boolean()" do @double.should_receive(:random_call).with(boolean()) @double.random_call(true) end it "accepts false as boolean()" do @double.should_receive(:random_call).with(boolean()) @double.random_call(false) end it "accepts fixnum as kind_of(Numeric)" do @double.should_receive(:random_call).with(kind_of(Numeric)) @double.random_call(1) end it "accepts float as an_instance_of(Numeric)" do @double.should_receive(:random_call).with(kind_of(Numeric)) @double.random_call(1.5) end it "accepts fixnum as instance_of(Fixnum)" do @double.should_receive(:random_call).with(instance_of(Fixnum)) @double.random_call(1) end it "does NOT accept fixnum as instance_of(Numeric)" do @double.should_not_receive(:random_call).with(instance_of(Numeric)) @double.random_call(1) end it "does NOT accept float as instance_of(Numeric)" do @double.should_not_receive(:random_call).with(instance_of(Numeric)) @double.random_call(1.5) end it "accepts string as anything()" do @double.should_receive(:random_call).with("a", anything(), "c") @double.random_call("a", "whatever", "c") end it "matches duck type with one method" do @double.should_receive(:random_call).with(duck_type(:length)) @double.random_call([]) end it "matches duck type with two methods" do @double.should_receive(:random_call).with(duck_type(:abs, :div)) @double.random_call(1) end it "matches no args against any_args()" do @double.should_receive(:random_call).with(any_args) @double.random_call() end it "matches one arg against any_args()" do @double.should_receive(:random_call).with(any_args) @double.random_call("a string") end it "matches no args against no_args()" do @double.should_receive(:random_call).with(no_args) @double.random_call() end it "matches hash with hash_including same hash" do @double.should_receive(:random_call).with(hash_including(:a => 1)) @double.random_call(:a => 1) end end context "handling block matchers" do it "matches arguments against RSpec expectations" do @double.should_receive(:random_call).with {|arg1, arg2, arr, *rest| expect(arg1).to eq 5 expect(arg2).to have_at_least(3).characters expect(arg2).to have_at_most(10).characters expect(arr.map {|i| i * 2}).to eq [2,4,6] expect(rest).to eq [:fee, "fi", 4] } @double.random_call 5, "hello", [1,2,3], :fee, "fi", 4 end it "does not eval the block as the return value" do eval_count = 0 @double.should_receive(:msg).with {|a| eval_count += 1} @double.msg(:ignore) expect(eval_count).to eq(1) end end context "handling non-matcher arguments" do it "matches non special symbol (can be removed when deprecated symbols are removed)" do @double.should_receive(:random_call).with(:some_symbol) @double.random_call(:some_symbol) end it "matches string against regexp" do @double.should_receive(:random_call).with(/bcd/) @double.random_call("abcde") end it "matches regexp against regexp" do @double.should_receive(:random_call).with(/bcd/) @double.random_call(/bcd/) end it "matches against a hash submitted and received by value" do @double.should_receive(:random_call).with(:a => "a", :b => "b") @double.random_call(:a => "a", :b => "b") end it "matches against a hash submitted by reference and received by value" do opts = {:a => "a", :b => "b"} @double.should_receive(:random_call).with(opts) @double.random_call(:a => "a", :b => "b") end it "matches against a hash submitted by value and received by reference" do opts = {:a => "a", :b => "b"} @double.should_receive(:random_call).with(:a => "a", :b => "b") @double.random_call(opts) end end end end spy-0.4.3/spec/spy/stub_spec.rb0000644000004100000410000000517312706267032016455 0ustar www-datawww-datarequire 'spec_helper' module RSpec module Mocks describe "A method stub" do before(:each) do @class = Class.new do class << self def existing_class_method existing_private_class_method end private def existing_private_class_method :original_value end end def existing_instance_method existing_private_instance_method end private def existing_private_instance_method :original_value end end @instance = @class.new @stub = Object.new end describe "using Spy::Subroutine.new" do it "returns declared value when message is received" do Spy::Subroutine.new(@instance, :msg).hook(force: true).and_return(:return_value) expect(@instance.msg).to equal(:return_value) end end it "instructs an instance to respond_to the message" do Spy::Subroutine.new(@instance, :msg).hook(force: true) expect(@instance).to respond_to(:msg) end it "instructs a class object to respond_to the message" do Spy::Subroutine.new(@class, :msg).hook(force: true) expect(@class).to respond_to(:msg) end it "yields a specified object" do Spy::Subroutine.new(@instance, :method_that_yields).hook(force: true).and_yield(:yielded_obj) current_value = :value_before @instance.method_that_yields {|val| current_value = val} expect(current_value).to eq :yielded_obj end it "yields a specified object and return another specified object" do yielded_obj = Spy.double("my mock") Spy::Subroutine.new(yielded_obj, :foo).hook(force: true) Spy::Subroutine.new(@instance, :method_that_yields_and_returns).hook(force: true).and_yield(yielded_obj).and_return(:baz) expect(@instance.method_that_yields_and_returns { |o| o.foo :bar }).to eq :baz end it "throws when told to" do Spy::Subroutine.new(@stub, :something).hook(force: true).and_throw(:up) expect { @stub.something }.to throw_symbol(:up) end it "throws with argument when told to" do Spy::Subroutine.new(@stub, :something).hook(force: true).and_throw(:up, 'high') expect { @stub.something }.to throw_symbol(:up, 'high') end it "overrides a pre-existing method" do Spy::Subroutine.new(@stub, :existing_instance_method).hook(force: true).and_return(:updated_stub_value) expect(@stub.existing_instance_method).to eq :updated_stub_value end end end end spy-0.4.3/spec/spy/null_object_mock_spec.rb0000644000004100000410000000441712706267032021011 0ustar www-datawww-datarequire 'spec_helper' module RSpec module Mocks describe "a double _not_ acting as a null object" do before(:each) do @double = Spy.double('non-null object') end it "says it does not respond to messages it doesn't understand" do expect(@double).not_to respond_to(:foo) end it "says it responds to messages it does understand" do Spy.on(@double, :foo) expect(@double).to respond_to(:foo) end it "raises an error when interpolated in a string as an integer" do expect { "%i" % @double }.to raise_error(TypeError) end end describe "a double acting as a null object" do before(:each) do @double = Spy.double('null object').as_null_object end it "says it responds to everything" do expect(@double).to respond_to(:any_message_it_gets) end it "allows explicit stubs" do Spy.on(@double, :foo) { "bar" } expect(@double.foo).to eq("bar") end it "allows explicit expectation" do spy = Spy.on(@double, :something) @double.something expect(spy).to have_been_called end it 'continues to return self from an explicit expectation' do spy = Spy.on(@double, :bar) expect(@double.foo.bar).to be(@double) expect(spy).to have_been_called end it 'returns an explicitly stubbed value from an expectation with no implementation' do spy = Spy.on(@double, :foo => "bar") expect(@double.foo).to eq("bar") expect(spy).to have_been_called end it "can be interpolated in a string as an integer" do # This form of string interpolation calls # @double.to_int.to_int.to_int...etc until it gets an integer, # and thus gets stuck in an infinite loop unless our double # returns an int value from #to_int. expect(("%i" % @double)).to eq("0") end end describe "#as_null_object" do it "sets the object to null_object" do obj = Spy.double('anything').as_null_object expect(obj).to be_null_object end end describe "#null_object?" do it "defaults to false" do obj = Spy.double('anything') expect(obj).not_to be_null_object end end end end spy-0.4.3/spec/spy/partial_mock_spec.rb0000644000004100000410000000376612706267032020153 0ustar www-datawww-datarequire 'spec_helper' module Spy describe "using a Partial Mock," do def stub(object, method_name) Spy::Subroutine.new(object, method_name).hook(force: true) end let(:object) { Object.new } it "names the class in the failure message" do spy = stub(object, :foo) expect(spy).to_not have_been_called end it "names the class in the failure message when expectation is on class" do spy = stub(Object, :foo) expect(spy).to_not have_been_called end it "does not conflict with @options in the object" do object.instance_eval { @options = Object.new } spy = stub(object, :blah) object.blah expect(spy).to have_been_called end it "uses reports nil in the error message" do allow_message_expectations_on_nil _nil = nil spy = stub(_nil, :foobar) _nil.foobar expect(spy).to have_been_called end end describe "Method visibility when using partial mocks" do def stub(o, method_name) Spy.on(o, method_name) end let(:klass) do Class.new do def public_method private_method protected_method end protected def protected_method; end private def private_method; end end end let(:object) { klass.new } it 'keeps public methods public' do spy = stub(object, :public_method) expect(object.public_methods).to include_method(:public_method) object.public_method expect(spy).to have_been_called end it 'keeps private methods private' do spy = stub(object, :private_method) expect(object.private_methods).to include_method(:private_method) object.public_method expect(spy).to have_been_called end it 'keeps protected methods protected' do spy = stub(object, :protected_method) expect(object.protected_methods).to include_method(:protected_method) object.public_method expect(spy).to have_been_called end end end spy-0.4.3/spec/spy/and_yield_spec.rb0000644000004100000410000000760012706267032017425 0ustar www-datawww-datarequire 'spec_helper' describe "Spy" do class Foo def method_that_accepts_a_block(&block) end end let(:obj) { Foo.new } describe "#and_yield" do context "with eval context as block argument" do it "evaluates the supplied block as it is read" do evaluated = false Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context| evaluated = true end expect(evaluated).to be_true end it "passes an eval context object to the supplied block" do Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context| expect(eval_context).not_to be_nil end end it "evaluates the block passed to the stubbed method in the context of the supplied eval context" do expected_eval_context = nil actual_eval_context = nil Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context| expected_eval_context = eval_context end obj.method_that_accepts_a_block do actual_eval_context = self end expect(actual_eval_context).to equal(expected_eval_context) end context "and no yielded arguments" do it "passes when expectations set on the eval context are met" do configured_eval_context = nil context_foo_spy = nil Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context| configured_eval_context = eval_context context_foo_spy = Spy::Subroutine.new(configured_eval_context, :foo).hook(force: true) end obj.method_that_accepts_a_block do foo end expect(context_foo_spy).to have_been_called end it "fails when expectations set on the eval context are not met" do configured_eval_context = nil context_foo_spy = nil Spy.on(obj, :method_that_accepts_a_block).and_yield do |eval_context| configured_eval_context = eval_context context_foo_spy = Spy::Subroutine.new(configured_eval_context, :foo).hook(force: true) end obj.method_that_accepts_a_block do # foo is not called here end expect(context_foo_spy).to_not have_been_called end end context "and yielded arguments" do it "passes when expectations set on the eval context and yielded arguments are met" do configured_eval_context = nil yielded_arg = Object.new context_foo_spy = nil Spy.on(obj, :method_that_accepts_a_block).and_yield(yielded_arg) do |eval_context| configured_eval_context = eval_context context_foo_spy = Spy::Subroutine.new(configured_eval_context, :foo).hook(force: true) Spy::Subroutine.new(yielded_arg, :bar).hook(force: true) end obj.method_that_accepts_a_block do |obj| obj.bar foo end expect(context_foo_spy).to have_been_called expect(Spy.get(yielded_arg, :bar)).to have_been_called end it "fails when expectations set on the eval context and yielded arguments are not met" do configured_eval_context = nil yielded_arg = Object.new context_foo_spy = nil Spy.on(obj, :method_that_accepts_a_block).and_yield(yielded_arg) do |eval_context| configured_eval_context = eval_context context_foo_spy = Spy::Subroutine.new(configured_eval_context, :foo).hook(force: true) Spy::Subroutine.new(yielded_arg, :bar).hook(force: true) end obj.method_that_accepts_a_block do |obj| # obj.bar is not called here # foo is not called here end expect(context_foo_spy).to_not have_been_called expect(Spy.get(yielded_arg, :bar)).to_not have_been_called end end end end end spy-0.4.3/spec/spy/any_instance_spec.rb0000644000004100000410000004750412706267032020157 0ustar www-datawww-datarequire 'spec_helper' module Spy describe "#any_instance" do class CustomErrorForAnyInstanceSpec < StandardError;end let(:klass) do Class.new do def existing_method; :existing_method_return_value; end def existing_method_with_arguments(arg_one, arg_two = nil); :existing_method_with_arguments_return_value; end def another_existing_method; end private def private_method; :private_method_return_value; end end end let(:existing_method_return_value){ :existing_method_return_value } context "with #stub" do it "does not suppress an exception when a method that doesn't exist is invoked" do Spy.on_instance_method(klass, :existing_method) expect { klass.new.bar }.to raise_error(NoMethodError) end context 'multiple methods' do it "allows multiple methods to be stubbed in a single invocation" do Spy.on_instance_method(klass, :existing_method => 'foo', :another_existing_method => 'bar') instance = klass.new expect(instance.existing_method).to eq('foo') expect(instance.another_existing_method).to eq('bar') end end context "behaves as 'every instance'" do it "stubs every instance in the spec" do Subroutine.new(klass, :foo, false).hook(force: true).and_return(result = Object.new) expect(klass.new.foo).to eq(result) expect(klass.new.foo).to eq(result) end it "stubs instance created before any_instance was called" do instance = klass.new Spy.on_instance_method(klass, :existing_method).and_return(result = Object.new) expect(instance.existing_method).to eq(result) end end context "with #and_return" do it "stubs a method that doesn't exist" do Spy.on_instance_method(klass, :existing_method).and_return(1) expect(klass.new.existing_method).to eq(1) end it "stubs a method that exists" do Spy.on_instance_method(klass, :existing_method).and_return(1) expect(klass.new.existing_method).to eq(1) end it "returns the same object for calls on different instances" do return_value = Object.new Spy.on_instance_method(klass, :existing_method).and_return(return_value) expect(klass.new.existing_method).to be(return_value) expect(klass.new.existing_method).to be(return_value) end end context "with #and_yield" do it "yields the value specified" do yielded_value = Object.new Spy.on_instance_method(klass, :existing_method).and_yield(yielded_value) klass.new.existing_method{|value| expect(value).to be(yielded_value)} end end context "with #and_raise" do it "stubs a method that doesn't exist" do Spy.on_instance_method(klass, :existing_method).and_raise(CustomErrorForAnyInstanceSpec) expect { klass.new.existing_method}.to raise_error(CustomErrorForAnyInstanceSpec) end it "stubs a method that exists" do Spy.on_instance_method(klass, :existing_method).and_raise(CustomErrorForAnyInstanceSpec) expect { klass.new.existing_method}.to raise_error(CustomErrorForAnyInstanceSpec) end end context "with a block" do it "stubs a method" do Spy.on_instance_method(klass, :existing_method) { 1 } expect(klass.new.existing_method).to eq(1) end it "returns the same computed value for calls on different instances" do Spy.on_instance_method(klass, :existing_method) { 1 + 2 } expect(klass.new.existing_method).to eq(klass.new.existing_method) end end context "core ruby objects" do it "works uniformly across *everything*" do Object.any_instance.stub(:foo).and_return(1) expect(Object.new.foo).to eq(1) end it "works with the non-standard constructor []" do Array.any_instance.stub(:foo).and_return(1) expect([].foo).to eq(1) end it "works with the non-standard constructor {}" do Hash.any_instance.stub(:foo).and_return(1) expect({}.foo).to eq(1) end it "works with the non-standard constructor \"\"" do String.any_instance.stub(:foo).and_return(1) expect("".foo).to eq(1) end it "works with the non-standard constructor \'\'" do String.any_instance.stub(:foo).and_return(1) expect(''.foo).to eq(1) end it "works with the non-standard constructor module" do Module.any_instance.stub(:foo).and_return(1) module RSpec::SampleRspecTestModule;end expect(RSpec::SampleRspecTestModule.foo).to eq(1) end it "works with the non-standard constructor class" do Class.any_instance.stub(:foo).and_return(1) class RSpec::SampleRspecTestClass;end expect(RSpec::SampleRspecTestClass.foo).to eq(1) end end end context "unstub implementation" do it "replaces the stubbed method with the original method" do Spy.on_instance_method(klass, :existing_method) klass.any_instance.unstub(:existing_method) expect(klass.new.existing_method).to eq(:existing_method_return_value) end it "removes all stubs with the supplied method name" do Spy.on_instance_method(klass, :existing_method).with(1) Spy.on_instance_method(klass, :existing_method).with(2) klass.any_instance.unstub(:existing_method) expect(klass.new.existing_method).to eq(:existing_method_return_value) end it "does not remove any expectations with the same method name" do klass.any_instance.should_receive(:existing_method_with_arguments).with(3).and_return(:three) Spy.on_instance_method(klass, :existing_method_with_arguments).with(1) Spy.on_instance_method(klass, :existing_method_with_arguments).with(2) klass.any_instance.unstub(:existing_method_with_arguments) expect(klass.new.existing_method_with_arguments(3)).to eq(:three) end it "raises a MockExpectationError if the method has not been stubbed" do expect { klass.any_instance.unstub(:existing_method) }.to raise_error(RSpec::Mocks::MockExpectationError, 'The method `existing_method` was not stubbed or was already unstubbed') end end context "with #should_receive" do let(:foo_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: foo' } let(:existing_method_expectation_error_message) { 'Exactly one instance should have received the following message(s) but didn\'t: existing_method' } context "with an expectation is set on a method which does not exist" do it "returns the expected value" do klass.any_instance.should_receive(:foo).and_return(1) expect(klass.new.foo(1)).to eq(1) end it "fails if an instance is created but no invocation occurs" do expect do klass.any_instance.should_receive(:foo) klass.new klass.rspec_verify end.to raise_error(RSpec::Mocks::MockExpectationError, foo_expectation_error_message) end it "fails if no instance is created" do expect do klass.any_instance.should_receive(:foo).and_return(1) klass.rspec_verify end.to raise_error(RSpec::Mocks::MockExpectationError, foo_expectation_error_message) end it "fails if no instance is created and there are multiple expectations" do expect do klass.any_instance.should_receive(:foo) klass.any_instance.should_receive(:bar) klass.rspec_verify end.to raise_error(RSpec::Mocks::MockExpectationError, 'Exactly one instance should have received the following message(s) but didn\'t: bar, foo') end it "allows expectations on instances to take priority" do klass.any_instance.should_receive(:foo) klass.new.foo instance = klass.new instance.should_receive(:foo).and_return(result = Object.new) expect(instance.foo).to eq(result) end context "behaves as 'exactly one instance'" do it "passes if subsequent invocations do not receive that message" do klass.any_instance.should_receive(:foo) klass.new.foo klass.new end it "fails if the method is invoked on a second instance" do instance_one = klass.new instance_two = klass.new expect do klass.any_instance.should_receive(:foo) instance_one.foo instance_two.foo end.to raise_error(RSpec::Mocks::MockExpectationError, "The message 'foo' was received by #{instance_two.inspect} but has already been received by #{instance_one.inspect}") end end context "normal expectations on the class object" do it "fail when unfulfilled" do expect do klass.any_instance.should_receive(:foo) klass.should_receive(:woot) klass.new.foo klass.rspec_verify end.to(raise_error(RSpec::Mocks::MockExpectationError) do |error| expect(error.message).not_to eq(existing_method_expectation_error_message) end) end it "pass when expectations are met" do klass.any_instance.should_receive(:foo) klass.should_receive(:woot).and_return(result = Object.new) klass.new.foo expect(klass.woot).to eq(result) end end end context "with an expectation is set on a method that exists" do it "returns the expected value" do klass.any_instance.should_receive(:existing_method).and_return(1) expect(klass.new.existing_method(1)).to eq(1) end it "fails if an instance is created but no invocation occurs" do expect do klass.any_instance.should_receive(:existing_method) klass.new klass.rspec_verify end.to raise_error(RSpec::Mocks::MockExpectationError, existing_method_expectation_error_message) end it "fails if no instance is created" do expect do klass.any_instance.should_receive(:existing_method) klass.rspec_verify end.to raise_error(RSpec::Mocks::MockExpectationError, existing_method_expectation_error_message) end it "fails if no instance is created and there are multiple expectations" do expect do klass.any_instance.should_receive(:existing_method) klass.any_instance.should_receive(:another_existing_method) klass.rspec_verify end.to raise_error(RSpec::Mocks::MockExpectationError, 'Exactly one instance should have received the following message(s) but didn\'t: another_existing_method, existing_method') end context "after any one instance has received a message" do it "passes if subsequent invocations do not receive that message" do klass.any_instance.should_receive(:existing_method) klass.new.existing_method klass.new end it "fails if the method is invoked on a second instance" do instance_one = klass.new instance_two = klass.new expect do klass.any_instance.should_receive(:existing_method) instance_one.existing_method instance_two.existing_method end.to raise_error(RSpec::Mocks::MockExpectationError, "The message 'existing_method' was received by #{instance_two.inspect} but has already been received by #{instance_one.inspect}") end end end end context "when resetting post-verification" do let(:space) { RSpec::Mocks::Space.new } context "existing method" do before(:each) do space.add(klass) end context "with stubbing" do context "public methods" do before(:each) do Spy.on_instance_method(klass, :existing_method).and_return(1) expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_true end it "restores the class to its original state after each example when no instance is created" do space.verify_all expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_false expect(klass.new.existing_method).to eq(existing_method_return_value) end it "restores the class to its original state after each example when one instance is created" do klass.new.existing_method space.verify_all expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_false expect(klass.new.existing_method).to eq(existing_method_return_value) end it "restores the class to its original state after each example when more than one instance is created" do klass.new.existing_method klass.new.existing_method space.verify_all expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_false expect(klass.new.existing_method).to eq(existing_method_return_value) end end context "private methods" do before :each do Spy.on_instance_method(klass, :private_method).and_return(:something) space.verify_all end it "cleans up the backed up method" do expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_false end it "restores a stubbed private method after the spec is run" do expect(klass.private_method_defined?(:private_method)).to be_true end it "ensures that the restored method behaves as it originally did" do expect(klass.new.send(:private_method)).to eq(:private_method_return_value) end end end context "with expectations" do context "private methods" do before :each do klass.any_instance.should_receive(:private_method).and_return(:something) klass.new.private_method space.verify_all end it "cleans up the backed up method" do expect(klass.method_defined?(:__existing_method_without_any_instance__)).to be_false end it "restores a stubbed private method after the spec is run" do expect(klass.private_method_defined?(:private_method)).to be_true end it "ensures that the restored method behaves as it originally did" do expect(klass.new.send(:private_method)).to eq(:private_method_return_value) end end context "ensures that the subsequent specs do not see expectations set in previous specs" do context "when the instance created after the expectation is set" do it "first spec" do klass.any_instance.should_receive(:existing_method).and_return(Object.new) klass.new.existing_method end it "second spec" do expect(klass.new.existing_method).to eq(existing_method_return_value) end end context "when the instance created before the expectation is set" do before :each do @instance = klass.new end it "first spec" do klass.any_instance.should_receive(:existing_method).and_return(Object.new) @instance.existing_method end it "second spec" do expect(@instance.existing_method).to eq(existing_method_return_value) end end end it "ensures that the next spec does not see that expectation" do klass.any_instance.should_receive(:existing_method).and_return(Object.new) klass.new.existing_method space.verify_all expect(klass.new.existing_method).to eq(existing_method_return_value) end end end context "with multiple calls to any_instance in the same example" do it "does not prevent the change from being rolled back" do Spy.on_instance_method(klass, :existing_method).and_return(false) Spy.on_instance_method(klass, :existing_method).and_return(true) klass.rspec_verify expect(klass.new).to respond_to(:existing_method) expect(klass.new.existing_method).to eq(existing_method_return_value) end end it "adds an class to the current space when #any_instance is invoked" do klass.any_instance expect(RSpec::Mocks::space.send(:receivers)).to include(klass) end it "adds an instance to the current space when stubbed method is invoked" do Spy.on_instance_method(klass, :foo) instance = klass.new instance.foo expect(RSpec::Mocks::space.send(:receivers)).to include(instance) end end context 'when used in conjunction with a `dup`' do it "doesn't cause an infinite loop" do Spy::Subroutine.new(Object, :some_method, false).hook(force: true) o = Object.new o.some_method expect { o.dup.some_method }.to_not raise_error(SystemStackError) end it "doesn't bomb if the object doesn't support `dup`" do klass = Class.new do undef_method :dup end Spy::Subroutine.new(Object, :some_method, false).hook(force: true) end it "doesn't fail when dup accepts parameters" do klass = Class.new do def dup(funky_option) end end Spy::Subroutine.new(Object, :some_method, false).hook(force: true) expect { klass.new.dup('Dup dup dup') }.to_not raise_error(ArgumentError) end end context "when directed at a method defined on a superclass" do let(:sub_klass) { Class.new(klass) } it "stubs the method correctly" do Spy.on_instance_method(klass, :existing_method).and_return("foo") expect(sub_klass.new.existing_method).to eq "foo" end it "mocks the method correctly" do instance_one = sub_klass.new instance_two = sub_klass.new expect do klass.any_instance.should_receive(:existing_method) instance_one.existing_method instance_two.existing_method end.to raise_error(RSpec::Mocks::MockExpectationError, "The message 'existing_method' was received by #{instance_two.inspect} but has already been received by #{instance_one.inspect}") end end context "when a class overrides Object#method" do let(:http_request_class) { Struct.new(:method, :uri) } it "stubs the method correctly" do http_request_class.any_instance.stub(:existing_method).and_return("foo") expect(http_request_class.new.existing_method).to eq "foo" end it "mocks the method correctly" do http_request_class.any_instance.should_receive(:existing_method).and_return("foo") expect(http_request_class.new.existing_method).to eq "foo" end end context "when used after the test has finished" do it "restores the original behavior of a stubbed method" do Spy.on_instance_method(klass, :existing_method).and_return(:stubbed_return_value) instance = klass.new expect(instance.existing_method).to eq :stubbed_return_value RSpec::Mocks.verify expect(instance.existing_method).to eq :existing_method_return_value end end end end spy-0.4.3/spec/spy/hash_excluding_matcher_spec.rb0000644000004100000410000000366212706267032022171 0ustar www-datawww-datarequire 'spec_helper' module Spy describe "Hash excluding matchers", broken: true do it "describes itself properly" do expect(HashExcludingMatcher.new(:a => 5).description).to eq "hash_not_including(:a=>5)" end describe "passing" do it "matches a hash without the specified key" do expect(hash_not_including(:c)).to eq({:a => 1, :b => 2}) end it "matches a hash with the specified key, but different value" do expect(hash_not_including(:b => 3)).to eq({:a => 1, :b => 2}) end it "matches a hash without the specified key, given as anything()" do expect(hash_not_including(:c => anything)).to eq({:a => 1, :b => 2}) end it "matches an empty hash" do expect(hash_not_including(:a)).to eq({}) end it "matches a hash without any of the specified keys" do expect(hash_not_including(:a, :b, :c)).to eq(:d => 7) end end describe "failing" do it "does not match a non-hash" do expect(hash_not_including(:a => 1)).not_to eq 1 end it "does not match a hash with a specified key" do expect(hash_not_including(:b)).not_to eq(:b => 2) end it "does not match a hash with the specified key/value pair" do expect(hash_not_including(:b => 2)).not_to eq(:a => 1, :b => 2) end it "does not match a hash with the specified key" do expect(hash_not_including(:a, :b => 3)).not_to eq(:a => 1, :b => 2) end it "does not match a hash with one of the specified keys" do expect(hash_not_including(:a, :b)).not_to eq(:b => 2) end it "does not match a hash with some of the specified keys" do expect(hash_not_including(:a, :b, :c)).not_to eq(:a => 1, :b => 2) end it "does not match a hash with one key/value pair included" do expect(hash_not_including(:a, :b, :c, :d => 7)).not_to eq(:d => 7) end end end end spy-0.4.3/spec/spy/mutate_const_spec.rb0000644000004100000410000004102512706267032020201 0ustar www-datawww-datarequire 'spec_helper' TOP_LEVEL_VALUE_CONST = 7 class TestClass M = :m N = :n class Nested class NestedEvenMore end end end class TestSubClass < TestClass P = :p end module Spy describe "Constant Mutating" do def reset_rspec_mocks Spy.teardown end def on_const(const_name) if const_name.include? "::" args = [recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, '')), const_name.split('::').last.to_sym] else args = [const_name.to_sym] end Spy.get_const(*args) || Spy.on_const(*args) end def stub_const(const_name, value) on_const(const_name).and_return(value) value end def hide_const(const_name) on_const(const_name).and_hide end shared_context "constant example methods" do |const_name| define_method :const do recursive_const_get(const_name) end define_method :parent_const do recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, '')) end define_method :last_const_part do const_name.split('::').last end end shared_examples_for "loaded constant stubbing" do |const_name| include_context "constant example methods", const_name let!(:original_const_value) { const } after { change_const_value_to(original_const_value) } def change_const_value_to(value) parent_const.send(:remove_const, last_const_part) parent_const.const_set(last_const_part, value) end it 'allows it to be stubbed' do expect(const).not_to eq(7) stub_const(const_name, 7) expect(const).to eq(7) end it 'resets it to its original value when rspec clears its mocks' do original_value = const expect(original_value).not_to eq(:a) stub_const(const_name, :a) reset_rspec_mocks expect(const).to be(original_value) end it 'returns the stubbed value' do expect(stub_const(const_name, 7)).to eq(7) end end shared_examples_for "loaded constant hiding" do |const_name| before do expect(recursive_const_defined?(const_name)).to be_true end it 'allows it to be hidden' do hide_const(const_name) expect(recursive_const_defined?(const_name)).to be_false end it 'resets the constant when rspec clear its mocks' do hide_const(const_name) reset_rspec_mocks expect(recursive_const_defined?(const_name)).to be_true end it 'returns nil' do expect(hide_const(const_name)).to be_nil end end shared_examples_for "unloaded constant stubbing" do |const_name| include_context "constant example methods", const_name before do expect(recursive_const_defined?(const_name)).to be_false end it 'allows it to be stubbed' do stub_const(const_name, 7) expect(const).to eq(7) end it 'removes the constant when rspec clears its mocks' do stub_const(const_name, 7) reset_rspec_mocks expect(recursive_const_defined?(const_name)).to be_false end it 'returns the stubbed value' do expect(stub_const(const_name, 7)).to eq(7) end it 'ignores the :transfer_nested_constants option if passed' do stub = Module.new stub_const(const_name, stub, :transfer_nested_constants => true) expect(stub.constants).to eq([]) end end shared_examples_for "unloaded constant hiding" do |const_name| include_context "constant example methods", const_name before do expect(recursive_const_defined?(const_name)).to be_false end it 'allows it to be hidden, though the operation has no effect' do hide_const(const_name) expect(recursive_const_defined?(const_name)).to be_false end it 'remains undefined after rspec clears its mocks' do hide_const(const_name) reset_rspec_mocks expect(recursive_const_defined?(const_name)).to be_false end it 'returns nil' do expect(hide_const(const_name)).to be_nil end end describe "#hide_const" do context 'for a loaded nested constant' do it_behaves_like "loaded constant hiding", "TestClass::Nested" end context 'for a loaded constant prefixed with ::' do it_behaves_like 'loaded constant hiding', "::TestClass" end context 'for an unloaded constant with nested name that matches a top-level constant' do it_behaves_like "unloaded constant hiding", "TestClass::Hash" it 'does not hide the top-level constant' do expect { hide_const("TestClass::Hash") }.to raise_error end end context 'for a loaded deeply nested constant' do it_behaves_like "loaded constant hiding", "TestClass::Nested::NestedEvenMore" end context 'for an unloaded unnested constant' do it_behaves_like "unloaded constant hiding", "X" end context 'for an unloaded nested constant' do it_behaves_like "unloaded constant hiding", "X::Y" end it 'can be hidden multiple times but still restores the original value properly' do orig_value = TestClass hide_const("TestClass") hide_const("TestClass") reset_rspec_mocks expect(TestClass).to be(orig_value) end it 'allows a constant to be hidden, then stubbed, restoring it to its original value properly' do orig_value = TOP_LEVEL_VALUE_CONST hide_const("TOP_LEVEL_VALUE_CONST") expect(recursive_const_defined?("TOP_LEVEL_VALUE_CONST")).to be_false stub_const("TOP_LEVEL_VALUE_CONST", 12345) expect(TOP_LEVEL_VALUE_CONST).to eq 12345 reset_rspec_mocks expect(TOP_LEVEL_VALUE_CONST).to eq orig_value end end describe "#stub_const" do context 'for a loaded unnested constant' do it_behaves_like "loaded constant stubbing", "TestClass" it 'can be stubbed multiple times but still restores the original value properly' do orig_value = TestClass stub1, stub2 = Module.new, Module.new stub_const("TestClass", stub1) stub_const("TestClass", stub2) reset_rspec_mocks expect(TestClass).to be(orig_value) end it 'allows nested constants to be transferred to a stub module' do tc_nested = TestClass::Nested stub = Module.new stub_const("TestClass", stub, :transfer_nested_constants => true) expect(stub::M).to eq(:m) expect(stub::N).to eq(:n) expect(stub::Nested).to be(tc_nested) end it 'does not transfer nested constants that are inherited from a superclass' do stub = Module.new stub_const("TestSubClass", stub, :transfer_nested_constants => true) expect(stub::P).to eq(:p) expect(defined?(stub::M)).to be_false expect(defined?(stub::N)).to be_false end it 'raises an error when asked to transfer a nested inherited constant' do original_tsc = TestSubClass expect { stub_const("TestSubClass", Module.new, :transfer_nested_constants => [:M]) }.to raise_error(ArgumentError) expect(TestSubClass).to be(original_tsc) end it 'allows nested constants to be selectively transferred to a stub module' do stub = Module.new stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N]) expect(stub::M).to eq(:m) expect(stub::N).to eq(:n) expect(defined?(stub::Nested)).to be_false end it 'raises an error if asked to transfer nested constants but given an object that does not support them' do original_tc = TestClass stub = Object.new expect { stub_const("TestClass", stub, :transfer_nested_constants => true) }.to raise_error(ArgumentError) expect(TestClass).to be(original_tc) expect { stub_const("TestClass", stub, :transfer_nested_constants => [:M]) }.to raise_error(ArgumentError) expect(TestClass).to be(original_tc) end it 'raises an error if asked to transfer nested constants on a constant that does not support nested constants' do stub = Module.new expect { stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => true) }.to raise_error(ArgumentError) expect(TOP_LEVEL_VALUE_CONST).to eq(7) expect { stub_const("TOP_LEVEL_VALUE_CONST", stub, :transfer_nested_constants => [:M]) }.to raise_error(ArgumentError) expect(TOP_LEVEL_VALUE_CONST).to eq(7) end it 'raises an error if asked to transfer a nested constant that is not defined' do original_tc = TestClass expect(defined?(TestClass::V)).to be_false stub = Module.new expect { stub_const("TestClass", stub, :transfer_nested_constants => [:V]) }.to raise_error(/cannot transfer nested constant.*V/i) expect(TestClass).to be(original_tc) end end context 'for a loaded nested constant' do it_behaves_like "loaded constant stubbing", "TestClass::Nested" end context 'for an unloaded constant with nested name that matches a top-level constant' do it_behaves_like "unloaded constant stubbing", "TestClass::Hash" end context 'for a loaded deeply nested constant' do it_behaves_like "loaded constant stubbing", "TestClass::Nested::NestedEvenMore" end context 'for an unloaded unnested constant' do it_behaves_like "unloaded constant stubbing", "X" end context 'for an unloaded constant nested within a loaded constant' do it_behaves_like "unloaded constant stubbing", "TestClass::X" it 'removes the unloaded constant but leaves the loaded constant when rspec resets its mocks' do expect(defined?(TestClass)).to be_true expect(defined?(TestClass::X)).to be_false stub_const("TestClass::X", 7) reset_rspec_mocks expect(defined?(TestClass)).to be_true expect(defined?(TestClass::X)).to be_false end it 'raises a helpful error if it cannot be stubbed due to an intermediary constant that is not a module' do expect(TestClass::M).to be_a(Symbol) expect { stub_const("TestClass::M::X", 5) }.to raise_error(/cannot stub/i) end end context 'for an unloaded constant nested deeply within a deeply nested loaded constant' do it_behaves_like "unloaded constant stubbing", "TestClass::Nested::NestedEvenMore::X::Y::Z" it 'removes the first unloaded constant but leaves the loaded nested constant when rspec resets its mocks' do expect(defined?(TestClass::Nested::NestedEvenMore)).to be_true expect(defined?(TestClass::Nested::NestedEvenMore::X)).to be_false stub_const("TestClass::Nested::NestedEvenMore::X::Y::Z", 7) reset_rspec_mocks expect(defined?(TestClass::Nested::NestedEvenMore)).to be_true expect(defined?(TestClass::Nested::NestedEvenMore::X)).to be_false end end end end describe Constant do def reset_rspec_mocks Spy.teardown end def on_const(const_name) if const_name.include? "::" args = [recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, '')), const_name.split('::').last.to_sym] else args = [const_name.to_sym] end Spy.get_const(*args) || Spy.on_const(*args) end def stub_const(const_name, value) on_const(const_name).and_return(value) end def hide_const(const_name) on_const(const_name).and_hide end def original(const_name) if const_name.include? "::" args = [recursive_const_get("Object::" + const_name.sub(/(::)?[^:]+\z/, '')), const_name.split('::').last.to_sym] else args = [const_name.to_sym] end Spy.get_const(*args) end describe ".original" do context 'for a previously defined unstubbed constant' do let(:const) { original("TestClass::M") } it("exposes its name") { expect(const.name).to eq("TestClass::M") } it("indicates it was previously defined") { expect(const).to be_previously_defined } it("indicates it has not been hidden") { expect(const).not_to be_hidden } it("exposes its original value") { expect(const.original_value).to eq(:m) } end context 'for a previously defined stubbed constant' do before { stub_const("TestClass::M", :other) } let(:const) { original("TestClass::M") } it("exposes its name") { expect(const.name).to eq("TestClass::M") } it("indicates it was previously defined") { expect(const).to be_previously_defined } it("indicates it has not been hidden") { expect(const).not_to be_hidden } it("exposes its original value") { expect(const.original_value).to eq(:m) } end context 'for a previously undefined stubbed constant' do before { stub_const("TestClass::Undefined", :other) } let(:const) { original("TestClass::Undefined") } it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") } it("indicates it was not previously defined") { expect(const).not_to be_previously_defined } it("indicates it has not been hidden") { expect(const).not_to be_hidden } it("returns nil for the original value") { expect(const.original_value).to be_nil } end context 'for a previously undefined unstubbed constant' do let(:const) { original("TestClass::Undefined") } it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") } it("indicates it was not previously defined") { expect(const).not_to be_previously_defined } it("indicates it has not been hidden") { expect(const).not_to be_hidden } it("returns nil for the original value") { expect(const.original_value).to be_nil } end context 'for a previously defined constant that has been stubbed twice' do before { stub_const("TestClass::M", 1) } before { stub_const("TestClass::M", 2) } let(:const) { original("TestClass::M") } it("exposes its name") { expect(const.name).to eq("TestClass::M") } it("indicates it was previously defined") { expect(const).to be_previously_defined } it("indicates it has not been hidden") { expect(const).not_to be_hidden } it("exposes its original value") { expect(const.original_value).to eq(:m) } end context 'for a previously undefined constant that has been stubbed twice' do before { stub_const("TestClass::Undefined", 1) } before { stub_const("TestClass::Undefined", 2) } let(:const) { original("TestClass::Undefined") } it("exposes its name") { expect(const.name).to eq("TestClass::Undefined") } it("indicates it was not previously defined") { expect(const).not_to be_previously_defined } it("indicates it has not been hidden") { expect(const).not_to be_hidden } it("returns nil for the original value") { expect(const.original_value).to be_nil } end context 'for a previously defined hidden constant' do before { hide_const("TestClass::M") } let(:const) { original("TestClass::M") } it("exposes its name") { expect(const.name).to eq("TestClass::M") } it("indicates it was previously defined") { expect(const).to be_previously_defined } it("indicates it has been hidden") { expect(const).to be_hidden } it("exposes its original value") { expect(const.original_value).to eq(:m) } end context 'for a previously defined constant that has been hidden twice' do before { hide_const("TestClass::M") } before { hide_const("TestClass::M") } let(:const) { original("TestClass::M") } it("exposes its name") { expect(const.name).to eq("TestClass::M") } it("indicates it was previously defined") { expect(const).to be_previously_defined } it("indicates it has been hidden") { expect(const).to be_hidden } it("exposes its original value") { expect(const.original_value).to eq(:m) } end end end end spy-0.4.3/spec/spy/hash_including_matcher_spec.rb0000644000004100000410000000537012706267032022161 0ustar www-datawww-datarequire 'spec_helper' module Spy describe "Hash including matchers", broken: true do it "describes itself properly" do expect(HashIncludingMatcher.new(:a => 1).description).to eq "hash_including(:a=>1)" end describe "passing" do it "matches the same hash" do expect(hash_including(:a => 1)).to eq({:a => 1}) end it "matches a hash with extra stuff" do expect(hash_including(:a => 1)).to eq({:a => 1, :b => 2}) end describe "when matching against other matchers" do it "matches an int against anything()" do expect(hash_including(:a => anything, :b => 2)).to eq({:a => 1, :b => 2}) end it "matches a string against anything()" do expect(hash_including(:a => anything, :b => 2)).to eq({:a => "1", :b => 2}) end end describe "when passed only keys or keys mixed with key/value pairs" do it "matches if the key is present" do expect(hash_including(:a)).to eq({:a => 1, :b => 2}) end it "matches if more keys are present" do expect(hash_including(:a, :b)).to eq({:a => 1, :b => 2, :c => 3}) end it "matches a string against a given key" do expect(hash_including(:a)).to eq({:a => "1", :b => 2}) end it "matches if passed one key and one key/value pair" do expect(hash_including(:a, :b => 2)).to eq({:a => 1, :b => 2}) end it "matches if passed many keys and one key/value pair" do expect(hash_including(:a, :b, :c => 3)).to eq({:a => 1, :b => 2, :c => 3, :d => 4}) end it "matches if passed many keys and many key/value pairs" do expect(hash_including(:a, :b, :c => 3, :e => 5)).to eq({:a => 1, :b => 2, :c => 3, :d => 4, :e => 5}) end end end describe "failing" do it "does not match a non-hash" do expect(hash_including(:a => 1)).not_to eq 1 end it "does not match a hash with a missing key" do expect(hash_including(:a => 1)).not_to eq(:b => 2) end it "does not match a hash with a missing key" do expect(hash_including(:a)).not_to eq(:b => 2) end it "does not match an empty hash with a given key" do expect(hash_including(:a)).not_to eq({}) end it "does not match a hash with a missing key when one pair is matching" do expect(hash_including(:a, :b => 2)).not_to eq(:b => 2) end it "does not match a hash with an incorrect value" do expect(hash_including(:a => 1, :b => 2)).not_to eq(:a => 1, :b => 3) end it "does not match when values are nil but keys are different" do expect(hash_including(:a => nil)).not_to eq(:b => nil) end end end end spy-0.4.3/.travis.yml0000644000004100000410000000023712706267032014501 0ustar www-datawww-datalanguage: ruby rvm: - 1.9.3 - 2.0.0 - jruby-19mode - ruby-head - rbx-19mode matrix: allow_failures: - rvm: ruby-head - rvm: rbx-19mode spy-0.4.3/lib/0000755000004100000410000000000012706267032013134 5ustar www-datawww-dataspy-0.4.3/lib/spy.rb0000644000004100000410000001400312706267032014272 0ustar www-datawww-datarequire "spy/exceptions" require "spy/core_ext/marshal" require "spy/agency" require "spy/api" require "spy/base" require "spy/call_log" require "spy/constant" require "spy/mock" require "spy/nest" require "spy/subroutine" require "spy/version" module Spy class << self # create a spy on given object # @param base_object # @param method_names *[Hash,Symbol] will spy on these methods and also set default return values # @return [Subroutine, Array] def on(base_object, *method_names) spies = method_names.map do |method_name| create_and_hook_spy(base_object, method_name) end.flatten spies.size > 1 ? spies : spies.first end # removes the spy from the from the given object # @param base_object # @param method_names *[Symbol] # @return [Subroutine, Array] def off(base_object, *method_names) removed_spies = method_names.map do |method_name| spy = Subroutine.get(base_object, method_name) if spy spy.unhook else raise NoSpyError, "#{method_name} was not hooked on #{base_object.inspect}." end end removed_spies.size > 1 ? removed_spies : removed_spies.first end # stubs the instance method of a given Class so all instance methods of this # class will have the given method stubbed # @param base_class [Class] The class you wish to stub the instance methods of # @param method_names *[Symbol, Hash] # @return [Spy,Array] def on_instance_method(base_class, *method_names) spies = method_names.map do |method_name| create_and_hook_spy(base_class, method_name, false) end.flatten spies.size > 1 ? spies : spies.first end # remove the stub from given Class # @param base_class [Class] # @param method_names *[Symbol] # @return [Spy] def off_instance_method(base_class, *method_names) removed_spies = method_names.map do |method_name| spy = Subroutine.get(base_class, method_name, false) if spy spy.unhook else raise NoSpyError, "#{method_name} was not hooked on #{base_class.inspect}." end end removed_spies.size > 1 ? removed_spies : removed_spies.first end # create a stub for constants on given module # @param base_module [Module] # @param constant_names *[Symbol, Hash] # @return [Constant, Array] def on_const(base_module, *constant_names) if base_module.is_a?(Hash) || base_module.is_a?(Symbol) constant_names.unshift(base_module) base_module = Object end spies = constant_names.map do |constant_name| case constant_name when Symbol Constant.on(base_module, constant_name) when Hash constant_name.map do |name, result| Constant.on(base_module, name).and_return(result) end else raise ArgumentError, "#{constant_name.class} is an invalid input, #on only accepts Symbol, and Hash" end end.flatten spies.size > 1 ? spies : spies.first end # removes stubs from given module # @param base_module [Module] # @param constant_names *[Symbol] # @return [Constant, Array] def off_const(base_module, *constant_names) if base_module.is_a?(Symbol) constant_names.unshift(base_module) base_module = Object end spies = constant_names.map do |constant_name| unless constant_name.is_a?(Symbol) raise ArgumentError, "#{constant_name.class} is an invalid input, #on only accepts Symbol, and Hash" end Constant.off(base_module, constant_name) end spies.size > 1 ? spies : spies.first end # Create a mock object from a given class # @param klass [Class] class you wish to mock # @param stubs *[Symbol, Hash] methods you with to stub # @return [Object] def mock(klass, *stubs) new_mock = Mock.new(klass).new if stubs.size > 0 on(new_mock, *stubs) end new_mock end # create a mock object from a given class with all the methods stubbed out # and returning nil unless specified otherwise. # @param klass [Class] class you wish to mock # @param stubs *[Symbol, Hash] methods you with to stub # @return [Object] def mock_all(klass, *stubs) mock_klass = Mock.new(klass) new_mock = mock_klass.new spies = stubs.size > 0 ? on(new_mock, *stubs) : [] unstubbed_methods = mock_klass.mocked_methods - spies.map(&:method_name) on(new_mock, *unstubbed_methods) if unstubbed_methods.size > 0 new_mock end # unhook all methods def teardown Agency.instance.dissolve! end # retrieve the spy from an object # @param base_object # @param method_names *[Symbol] # @return [Subroutine, Array] def get(base_object, *method_names) spies = method_names.map do |method_name| Subroutine.get(base_object, method_name) end spies.size > 1 ? spies : spies.first end # retrieve the constant spies from an object # @param base_module # @param constant_names *[Symbol] # @return [Constant, Array] def get_const(base_module, *constant_names) if base_module.is_a?(Symbol) constant_names.unshift(base_module) base_module = Object end spies = constant_names.map do |constant_name| Constant.get(base_module, constant_name) end spies.size > 1 ? spies : spies.first end private def create_and_hook_spy(base_object, method_name, singleton_method = true) case method_name when String, Symbol Subroutine.on(base_object, method_name, singleton_method) when Hash method_name.map do |name, result| Subroutine.on(base_object, name, singleton_method).and_return(result) end else raise ArgumentError, "#{method_name.class} is an invalid input, #on only accepts String, Symbol, and Hash" end end end end spy-0.4.3/lib/spy/0000755000004100000410000000000012706267032013747 5ustar www-datawww-dataspy-0.4.3/lib/spy/constant.rb0000644000004100000410000000730312706267032016130 0ustar www-datawww-datamodule Spy class Constant include Base # @!attribute [r] base_module # @return [Module] the module that is being watched # # @!attribute [r] constant_name # @return [Symbol] the name of the constant that is/will be stubbed # # @!attribute [r] original_value # @return [Object] the original value that was set when it was hooked attr_reader :base_module, :constant_name, :original_value # @param base_module [Module] the module this spy should be on # @param constant_name [Symbol] the constant this spy is watching def initialize(base_module, constant_name) raise ArgumentError, "#{base_module.inspect} is not a kind of Module" unless base_module.is_a? Module raise ArgumentError, "#{constant_name.inspect} is not a kind of Symbol" unless constant_name.is_a? Symbol @base_module, @constant_name = base_module, constant_name.to_sym @original_value = @new_value = @previously_defined = nil end # full name of spied constant def name "#{base_module.name}::#{constant_name}" end # stashes the original constant then overwrites it with nil # @param opts [Hash{force => false}] set :force => true if you want it to ignore if the constant exists # @return [self] def hook(opts = {}) opts[:force] ||= false Nest.fetch(base_module).add(self) Agency.instance.recruit(self) @previously_defined = currently_defined? if previously_defined? || !opts[:force] @original_value = base_module.const_get(constant_name, false) end and_return(@new_value) self end # restores the original value of the constant or unsets it if it was unset # @return [self] def unhook Nest.get(base_module).remove(self) Agency.instance.retire(self) and_return(@original_value) if previously_defined? @original_value = @previously_defined = nil self end # unsets the constant # @return [self] def and_hide base_module.send(:remove_const, constant_name) if currently_defined? self end # sets the constant to the requested value # @param value [Object] # @return [self] def and_return(value) @new_value = value and_hide base_module.const_set(constant_name, @new_value) self end # checks to see if this spy is hooked? # @return [Boolean] def hooked? self.class.get(base_module, constant_name) == self end # checks to see if the constant is hidden? # @return [Boolean] def hidden? hooked? && currently_defined? end # checks to see if the constant is currently defined? # @return [Boolean] def currently_defined? base_module.const_defined?(constant_name, false) end # checks to see if the constant is previously defined? # @return [Boolean] def previously_defined? @previously_defined end class << self # finds existing spy or creates a new constant spy and hooks the constant # @return [Constant] def on(base_module, constant_name) new(base_module, constant_name).hook end # retrieves the spy for given constant and module and unhooks the constant # from the module # @return [Constant] def off(base_module, constant_name) spy = get(base_module, constant_name) raise NoSpyError, "#{constant_name} was not spied on #{base_module}" unless spy spy.unhook end # retrieves the spy for given constnat and module or returns nil # @return [Nil, Constant] def get(base_module, constant_name) nest = Nest.get(base_module) if nest nest.get(constant_name) end end end end end spy-0.4.3/lib/spy/nest.rb0000644000004100000410000000431712706267032015252 0ustar www-datawww-datamodule Spy # This class manages all the Constant Mutations for a given Module class Nest # @!attribute [r] base_module # @return [Module] The module that the Nest is managing # # @!attribute [r] constant_spies # @return [Hash] The module that the Nest is managing attr_reader :base_module def initialize(base_module) raise ArgumentError, "#{base_module} is not a kind of Module" unless base_module.is_a?(Module) @base_module = base_module @constant_spies = {} end # records that the spy is hooked # @param spy [Constant] # @return [self] def add(spy) if @constant_spies[spy.constant_name] raise AlreadyStubbedError, "#{spy.constant_name} has already been stubbed" else @constant_spies[spy.constant_name] = spy end self end # removes the spy from the records # @param spy [Constant] # @return [self] def remove(spy) if @constant_spies[spy.constant_name] == spy @constant_spies.delete(spy.constant_name) else raise NoSpyError, "#{spy.constant_name} was not stubbed on #{base_module.name}" end self end # returns a spy if the constant was added # @param constant_name [Symbol] # @return [Constant, nil] def get(constant_name) @constant_spies[constant_name] end # checks to see if a given constant is hooked # @param constant_name [Symbol] # @return [Boolean] def hooked?(constant_name) !!get(constant_name) end # list all the constants that are being stubbed # @return [Array] def hooked_constants @constant_spies.keys end class << self # retrieves the nest for a given module # @param base_module [Module] # @return [Nil, Nest] def get(base_module) all[base_module.name] end # retrieves the nest for a given module or creates it # @param base_module [Module] # @return [Nest] def fetch(base_module) all[base_module.name] ||= self.new(base_module) end # returns all the hooked constants # @return [Hash] def all @all ||= {} end end end end spy-0.4.3/lib/spy/core_ext/0000755000004100000410000000000012706267032015557 5ustar www-datawww-dataspy-0.4.3/lib/spy/core_ext/marshal.rb0000644000004100000410000000120012706267032017524 0ustar www-datawww-datamodule Marshal class << self # @private def dump_with_mocks(*args) object = args.shift spies = Spy::Subroutine.get_spies(object) if spies.empty? return dump_without_mocks(*args.unshift(object)) end spy_hook_options = spies.map do |spy| [spy.hook_opts, spy.unhook] end begin dump_without_mocks(*args.unshift(object.dup)) ensure spy_hook_options.each do |hook_opts, spy| spy.hook(hook_opts) end end end alias_method :dump_without_mocks, :dump undef_method :dump alias_method :dump, :dump_with_mocks end end spy-0.4.3/lib/spy/integration.rb0000644000004100000410000000230212706267032016614 0ustar www-datawww-datarequire 'spy' module Spy if defined?(::MiniTest::Unit::TestCase) || defined?(::Minitest::Test) module MiniTestAdapter include API def after_teardown super Spy.teardown end end if defined?(::MiniTest::Unit::TestCase) && !::MiniTest::Unit::TestCase.include?(MiniTestAdapter) ::MiniTest::Unit::TestCase.send(:include, MiniTestAdapter) end if defined?(::Minitest::Test) && !::Minitest::Test.include?(MiniTestAdapter) ::Minitest::Test.send(:include, MiniTestAdapter) end end if defined?(::Test::Unit::TestCase) && !(defined?(::MiniTest::Unit::TestCase) && (::Test::Unit::TestCase < ::MiniTest::Unit::TestCase)) && !(defined?(::MiniTest::Spec) && (::Test::Unit::TestCase < ::MiniTest::Spec)) module TestUnitAdapter include API def self.included(mod) mod.teardown :spy_teardown, :after => :append end def spy_teardown Spy.teardown end end ::Test::Unit::TestCase.send(:include, TestUnitAdapter) end class RspecAdapter include API def setup_mocks_for_rspec end def verify_mocks_for_rspec end def teardown_mocks_for_rspec Spy.teardown end end end spy-0.4.3/lib/spy/mock.rb0000644000004100000410000000667612706267032015244 0ustar www-datawww-datamodule Spy # A Mock is an object that has all the same methods as the given class. # Each method however will raise a NeverHookedError if it hasn't been stubbed. # If you attempt to stub a method on the mock that doesn't exist on the # original class it will raise an error. module Mock include Base CLASSES_NOT_TO_OVERRIDE = [Enumerable, Numeric, Comparable, Class, Module, Object] METHODS_NOT_TO_OVERRIDE = [:initialize, :method] def initialize(&mock_method) Agency.instance.recruit(self) end # the only method that doesn't work correctly of a mock if inherited. We # overwite for compatibility. # @param other [Class] # @return [Boolean] def instance_of?(other) other == self.class end # returns the original class method if the current method is a mock_method # @param method_name [Symbol, String] # @return [Method] def method(method_name) new_method = super parameters = new_method.parameters if parameters.size >= 1 && parameters.last.last == :mock_method self.class.instance_method(method_name).bind(self) else new_method end end class << self # This will create a new Mock class with all the instance methods of given # klass mocked out. # @param klass [Class] # @return [Class] def new(klass) mock_klass = Class.new(klass) mock_klass.class_exec do alias :_mock_class :class private :_mock_class define_method(:class) do klass end include Mock end mock_klass end private def included(mod) method_classes = classes_to_override_methods(mod) mocked_methods = [] [:public, :protected, :private].each do |visibility| get_inherited_methods(method_classes, visibility).each do |method_name| mocked_methods << method_name args = args_for_method(mod.instance_method(method_name)) mod.class_eval <<-DEF_METHOD, __FILE__, __LINE__ + 1 def #{method_name}(#{args}) raise ::Spy::NeverHookedError, "'#{method_name}' was never hooked on mock spy." end DEF_METHOD mod.send(visibility, method_name) end end mod.define_singleton_method(:mocked_methods) do mocked_methods end end def classes_to_override_methods(mod) method_classes = mod.ancestors method_classes.shift method_classes.delete(self) CLASSES_NOT_TO_OVERRIDE.each do |klass| index = method_classes.index(klass) method_classes.slice!(index..-1) if index end method_classes end def get_inherited_methods(klass_ancestors, visibility) instance_methods = klass_ancestors.map do |klass| klass.send("#{visibility}_instance_methods".to_sym, false) end instance_methods.flatten! instance_methods.uniq! instance_methods - METHODS_NOT_TO_OVERRIDE end def args_for_method(method) args = method.parameters args.map! do |type,name| name ||= :args case type when :req name when :opt "#{name} = nil" when :rest "*#{name}" end end args.compact! args << "&mock_method" args.join(",") end end end end spy-0.4.3/lib/spy/api.rb0000644000004100000410000000341112706267032015044 0ustar www-datawww-datamodule Spy module API DidNotReceiveError = Class.new(Spy::Error) def assert_received(base_object, method_name) assert Subroutine.get(base_object, method_name).has_been_called?, "#{method_name} was not called on #{base_object.inspect}" end def assert_received_with(base_object, method_name, *args, &block) assert Subroutine.get(base_object, method_name).has_been_called_with?(*args, &block), "#{method_name} was not called on #{base_object.inspect} with #{args.inspect}" end def have_received(method_name) HaveReceived.new(method_name) end class HaveReceived attr_reader :method_name, :actual def initialize(method_name) @method_name = method_name @with = nil end def matches?(actual) @actual = actual case @with when Proc spy.has_been_called_with?(&@with) when Array spy.has_been_called_with?(*@with) else spy.has_been_called? end end def with(*args) @with = block_given? ? Proc.new : args self end def failure_message_for_should "expected #{actual.inspect} to have received #{method_name.inspect}#{args_message}" end def failure_message_for_should_not "expected #{actual.inspect} to not have received #{method_name.inspect}#{args_message}, but did" end def description "to have received #{method_name.inspect}#{args_message}" end private def args_message case @with when Array " with #{@with.inspect}" when Proc " with given block" end end def spy @spy ||= Subroutine.get(@actual, @method_name) end end end end spy-0.4.3/lib/spy/version.rb0000644000004100000410000000004312706267032015756 0ustar www-datawww-datamodule Spy VERSION = "0.4.3" end spy-0.4.3/lib/spy/subroutine.rb0000644000004100000410000003062012706267032016474 0ustar www-datawww-datamodule Spy class Subroutine include Base # @!attribute [r] base_object # @return [Object] the object that is being watched # # @!attribute [r] method_name # @return [Symbol] the name of the method that is being watched # # @!attribute [r] singleton_method # @return [Boolean] if the spied method is a singleton_method or not # # @!attribute [r] calls # @return [Array] the messages that have been sent to the method # # @!attribute [r] original_method # @return [Method] the original method that was hooked if it existed # # @!attribute [r] original_method_visibility # @return [Method] the original visibility of the method that was hooked if it existed # # @!attribute [r] hook_opts # @return [Hash] the options that were sent when it was hooked attr_reader :base_object, :method_name, :singleton_method, :calls, :original_method, :original_method_visibility, :hook_opts # set what object and method the spy should watch # @param object # @param method_name # @param singleton_method spy on the singleton method or the normal method def initialize(object, method_name, singleton_method = true) @base_object, @method_name = object, method_name @singleton_method = singleton_method reset! end # hooks the method into the object and stashes original method if it exists # @param [Hash] opts what do do when hooking into a method # @option opts [Boolean] force (false) if set to true will hook the method even if it doesn't exist # @option opts [Symbol<:public, :protected, :private>] visibility overrides visibility with whatever method is given # @return [self] def hook(opts = {}) raise AlreadyHookedError, "#{base_object} method '#{method_name}' has already been hooked" if self.class.get(base_object, method_name, singleton_method) @hook_opts = opts @original_method_visibility = method_visibility_of(method_name) hook_opts[:visibility] ||= original_method_visibility if original_method_visibility || !hook_opts[:force] @original_method = current_method end define_method_with = singleton_method ? :define_singleton_method : :define_method base_object.send(define_method_with, method_name, override_method) if [:public, :protected, :private].include? hook_opts[:visibility] method_owner.send(hook_opts[:visibility], method_name) end Agency.instance.recruit(self) @was_hooked = true self end # unhooks method from object # @return [self] def unhook raise NeverHookedError, "'#{method_name}' method has not been hooked" unless hooked? if original_method && method_owner == original_method.owner method_owner.send(:define_method, method_name, original_method) method_owner.send(original_method_visibility, method_name) if original_method_visibility else method_owner.send(:remove_method, method_name) end clear_method! Agency.instance.retire(self) self end # is the spy hooked? # @return [Boolean] def hooked? self == self.class.get(base_object, method_name, singleton_method) end # @overload and_return(value) # @overload and_return(&block) # # Tells the spy to return a value when the method is called. # # If a block is sent it will execute the block when the method is called. # The airty of the block will be checked against the original method when # you first call `and_return` and when the method is called. # # If you want to disable the arity checking just pass `{force: true}` to the # value # # @example # spy.and_return(true) # spy.and_return { true } # spy.and_return(force: true) { |invalid_arity| true } # # @return [self] def and_return(value = nil) @do_not_check_plan_arity = false if block_given? if value.is_a?(Hash) && value.has_key?(:force) @do_not_check_plan_arity = !!value[:force] elsif !value.nil? raise ArgumentError, "value and block conflict. Choose one" end @plan = Proc.new check_for_too_many_arguments!(@plan) else @plan = Proc.new { value } end self end # Tells the object to yield one or more args to a block when the message is received. # @return [self] def and_yield(*args) yield eval_context = Object.new if block_given? @plan = Proc.new do |&block| eval_context.instance_exec(*args, &block) end self end # tells the spy to call the original method # @return [self] def and_call_through @plan = Proc.new do |*args, &block| if original_method original_method.call(*args, &block) else base_object.send(:method_missing, method_name, *args, &block) end end self end # @overload and_raise # @overload and_raise(ExceptionClass) # @overload and_raise(ExceptionClass, message) # @overload and_raise(exception_instance) # # Tells the object to raise an exception when the message is received. # # @note # # When you pass an exception class, the MessageExpectation will raise # an instance of it, creating it with `exception` and passing `message` # if specified. If the exception class initializer requires more than # one parameters, you must pass in an instance and not the class, # otherwise this method will raise an ArgumentError exception. # # @return [self] def and_raise(exception = RuntimeError, message = nil) if exception.respond_to?(:exception) exception = message ? exception.exception(message) : exception.exception end @plan = Proc.new { raise exception } end # @overload and_throw(symbol) # @overload and_throw(symbol, object) # # Tells the object to throw a symbol (with the object if that form is # used) when the message is received. # # @return [self] def and_throw(*args) @plan = Proc.new { throw(*args) } self end # if the method was called it will return true # @return [Boolean] def has_been_called? raise NeverHookedError unless @was_hooked calls.size > 0 end # check if the method was called with the exact arguments # @param args Arguments that should have been sent to the method # @return [Boolean] def has_been_called_with?(*args) raise NeverHookedError unless @was_hooked match = block_given? ? Proc.new : proc { |call| call.args == args } calls.any?(&match) end # invoke that the method has been called. You really shouldn't use this # method. def invoke(object, args, block, called_from) check_arity!(args.size) result = if @plan check_for_too_many_arguments!(@plan) @plan.call(*args, &block) end ensure calls << CallLog.new(object, called_from, args, block, result) end # reset the call log def reset! @was_hooked = false @calls = [] clear_method! true end private # this returns a lambda that calls the spy object. # we use eval to set the spy object id as a parameter so it can be extracted # and looked up later using `Method#parameters` SPY_ARGS_PREFIX='__spy_args_'.freeze def override_method eval <<-METHOD, binding, __FILE__, __LINE__ + 1 __method_spy__ = self lambda do |*#{SPY_ARGS_PREFIX}#{self.object_id}, &block| __method_spy__.invoke(self, #{SPY_ARGS_PREFIX}#{self.object_id}, block, caller(1)[0]) end METHOD end def clear_method! @hooked = @do_not_check_plan_arity = false @hook_opts = @original_method = @arity_range = @original_method_visibility = @method_owner= nil end def method_visibility_of(method_name, all = true) if singleton_method if base_object.public_methods(all).include?(method_name) :public elsif base_object.protected_methods(all).include?(method_name) :protected elsif base_object.private_methods(all).include?(method_name) :private end else if base_object.public_instance_methods(all).include?(method_name) :public elsif base_object.protected_instance_methods(all).include?(method_name) :protected elsif base_object.private_instance_methods(all).include?(method_name) :private end end end def check_arity!(arity) return unless arity_range if arity < arity_range.min raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.min})") elsif arity > arity_range.max raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.max})") end true end def check_for_too_many_arguments!(block) return if @do_not_check_plan_arity || arity_range.nil? min_arity = block.arity min_arity = min_arity.abs - 1 if min_arity < 0 if min_arity > arity_range.max raise ArgumentError.new("block requires #{min_arity} arguments while original_method require a maximum of #{arity_range.max}") end end def arity_range @arity_range ||= if original_method min = max = 0 original_method.parameters.each do |type,_| case type when :req min += 1 max += 1 when :opt max += 1 when :rest max = Float::INFINITY end end (min..max) end end def current_method singleton_method ? base_object.method(method_name) : base_object.instance_method(method_name) end def method_owner @method_owner ||= current_method.owner end class << self # retrieve the method spy from an object or create a new one # @param base_object # @param method_name [Symbol] # @param singleton_method [Boolean] this a singleton method or a instance method? # @return [Array] def on(base_object, method_name, singleton_method = true) new(base_object, method_name, singleton_method).hook end def off(base_object, method_name, singleton_method = true) spy = get(base_object, method_name, singleton_method = true) raise NoSpyError, "#{method_name} was not spied on #{base_object}" unless spy spy.unhook end # retrieve the method spy from an object or return nil # @param base_object # @param method_name [Symbol] # @param singleton_method [Boolean] this a singleton method or a instance method? # @return [Array] def get(base_object, method_name, singleton_method = true) if singleton_method if base_object.respond_to?(method_name, true) spied_method = base_object.method(method_name) end elsif (base_object.public_instance_methods + base_object.protected_instance_methods + base_object.private_instance_methods).include?(method_name) spied_method = base_object.instance_method(method_name) end if spied_method Agency.instance.find(get_spy_id(spied_method)) end end # retrieve all the spies from a given object # @param base_object # @param singleton_method [Boolean] (true) only get singleton_method_spies # @return [Array] def get_spies(base_object, singleton_methods = true) all_methods = if singleton_methods base_object.public_methods(false) + base_object.protected_methods(false) + base_object.private_methods(false) else base_object.public_instance_methods(false) + base_object.protected_instance_methods(false) + base_object.private_instance_methods(false) end all_methods.map do |method_name| Agency.instance.find(get_spy_id(base_object.method(method_name))) end.compact end # @private def get_spy_id(method) if method.parameters[0].is_a?(Array) && method.parameters[0][1] raw_id = method.parameters[0][1].to_s if raw_id.start_with?(SPY_ARGS_PREFIX) raw_id[SPY_ARGS_PREFIX.length..-1].to_i end end end end end end spy-0.4.3/lib/spy/base.rb0000644000004100000410000000004312706267032015203 0ustar www-datawww-datamodule Spy module Base end end spy-0.4.3/lib/spy/agency.rb0000644000004100000410000000313512706267032015544 0ustar www-datawww-datarequire 'singleton' module Spy # Manages all the spies class Agency include Singleton # @private def initialize clear! end # given a spy ID it will return the associated spy # @param id [Integer] spy object id # @return [Nil, Subroutine, Constant, Double] def find(id) @spies[id] end # Record that a spy was initialized and hooked # @param spy [Subroutine, Constant, Double] # @return [spy] def recruit(spy) raise AlreadyStubbedError if @spies[spy.object_id] check_spy!(spy) @spies[spy.object_id] = spy end # remove spy from the records # @param spy [Subroutine, Constant, Double] # @return [spy] def retire(spy) raise NoSpyError unless @spies[spy.object_id] @spies.delete(spy.object_id) end # checks to see if a spy is hooked # @param spy [Subroutine, Constant, Double] # @return [Boolean] def active?(spy) check_spy!(spy) @spies.has_key?(spy.object_id) end # unhooks all spies and clears records # @return [self] def dissolve! @spies.values.each do |spy| spy.unhook if spy.respond_to?(:unhook) end clear! end # clears records # @return [self] def clear! @spies = {} self end # returns all the spies that have been initialized since the creation of # this agency # @return [Array] def spies @spies.values end private def check_spy!(spy) raise ArgumentError, "#{spy}, was not a spy" unless spy.is_a?(Base) end end end spy-0.4.3/lib/spy/exceptions.rb0000644000004100000410000000035512706267032016460 0ustar www-datawww-datamodule Spy Error = Class.new(StandardError) AlreadyStubbedError = Class.new(Error) AlreadyHookedError = Class.new(Error) NotHookedError = Class.new(Error) NeverHookedError = Class.new(Error) NoSpyError = Class.new(Error) end spy-0.4.3/lib/spy/call_log.rb0000644000004100000410000000136412706267032016054 0ustar www-datawww-datamodule Spy class CallLog # @!attribute [r] object # @return [Object] object that the method was called from # # @!attribute [r] called_from # @return [String] where the method was called from # # @!attribute [r] args # @return [Array] arguments were sent to the method # # @!attribute [r] block # @return [Proc] the block that was sent to the method # # @!attribute [r] result # @return The result of the method of being stubbed, or called through attr_reader :object, :called_from, :args, :block, :result def initialize(object, called_from, args, block, result) @object, @called_from, @args, @block, @result = object, called_from, args, block, result end end end spy-0.4.3/spy.gemspec0000644000004100000410000000246012706267032014550 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'spy/version' Gem::Specification.new do |gem| gem.name = "spy" gem.version = Spy::VERSION gem.required_ruby_version = '>= 1.9.3' gem.license = 'MIT' gem.authors = ["Ryan Ong"] gem.email = ["ryanong@gmail.com"] gem.summary = %q{A simple modern mocking library that uses the spy pattern and checks method's existence and arity.} gem.description = %q{Spy is a mocking library that was made for the modern age. It supports only 1.9.3+. Spy by default will raise an error if you attempt to stub a method that doesn't exist or call the stubbed method with the wrong arity.} gem.homepage = "https://github.com/ryanong/spy" gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.add_development_dependency('pry') gem.add_development_dependency('pry-nav') gem.add_development_dependency('minitest', '>= 4.5.0') gem.add_development_dependency('rspec-core') gem.add_development_dependency('rspec-expectations') gem.add_development_dependency('coveralls') end spy-0.4.3/test/0000755000004100000410000000000012706267032013345 5ustar www-datawww-dataspy-0.4.3/test/test_helper.rb0000644000004100000410000000033612706267032016212 0ustar www-datawww-datarequire 'bundler/setup' require 'minitest/autorun' require 'pry' require 'pry-nav' require 'coveralls' Coveralls.wear! require 'spy' Dir.glob(File.expand_path("../support/*", __FILE__)).each do |file| require file end spy-0.4.3/test/spy/0000755000004100000410000000000012706267032014160 5ustar www-datawww-dataspy-0.4.3/test/spy/test_subroutine.rb0000644000004100000410000001560512706267032017752 0ustar www-datawww-datarequire 'test_helper' module Spy class TestSubroutine < Minitest::Test def spy_on(base_object, method_name) Subroutine.new(base_object, method_name).hook end def setup @pen = Pen.new end def teardown Spy::Agency.instance.dissolve! end def test_spy_on_hook_and_saves_spy pen_write_spy = spy_on(@pen, :write).and_return("hello") assert_equal "hello", @pen.write(nil) assert_kind_of Subroutine, pen_write_spy assert_equal [pen_write_spy], Agency.instance.spies assert pen_write_spy.has_been_called? end def test_spy_can_hook_and_record_a_method_call pen_write_spy = spy_on(@pen, :write) refute pen_write_spy.has_been_called? @pen.write("hello") assert pen_write_spy.has_been_called? assert_empty @pen.written end def test_spy_can_hook_and_record_a_method_call_on_a_constant another_spy = spy_on(Pen, :another) refute another_spy.has_been_called? assert_nil Pen.another assert another_spy.has_been_called? another_spy.unhook assert_equal "another", Pen.another end def test_spy_can_hook_and_record_a_meta_method_call_on_a_constant assert_equal "meta_method", Pen.meta_method meta_spy = spy_on(Pen, :meta_method) refute meta_spy.has_been_called? assert_nil Pen.meta_method assert meta_spy.has_been_called? meta_spy.unhook assert_equal "meta_method", Pen.meta_method end def test_spy_can_hook_record_and_unhook_a_meta_method assert_equal "meta_method", @pen.meta_method meta_spy = spy_on(@pen, :meta_method) refute meta_spy.has_been_called? assert_nil @pen.meta_method assert meta_spy.has_been_called? meta_spy.unhook assert_equal "meta_method", @pen.meta_method end def test_spy_can_unhook_a_method pen_write_spy = spy_on(@pen, :write) pen_write_spy.unhook assert_equal "hello", @pen.write("hello") refute pen_write_spy.has_been_called? end def test_spy_cannot_hook_a_non_existent_method spy = Subroutine.new(@pen, :no_method) assert_raises NameError do spy.hook end end def test_spy_can_hook_a_non_existent_method_if_param_set Subroutine.new(@pen, :no_method).hook(force:true).and_return(:yep) assert_equal :yep, @pen.no_method end def test_spy_and_return_returns_the_set_value result = "hello world" spy_on(@pen, :write).and_return(result) assert_equal result, @pen.write(nil) end def test_spy_and_return_can_call_a_block result = "hello world" spy_on(@pen, :write).and_return {}.and_return do |string| string.reverse end assert_equal result.reverse, @pen.write(result) assert_empty @pen.written end def test_spy_and_return_can_call_a_block_raises_when_there_is_an_arity_mismatch write_spy = spy_on(@pen, :write) write_spy.and_return do |*args| end write_spy.and_return do |string, *args| end assert_raises ArgumentError do write_spy.and_return do |string, b| end end write_spy.and_return(force: true) do |string, b| end end def test_spy_and_return_can_call_a_block_that_recieves_a_block string = "hello world" spy_on(@pen, :write_block).and_return do |&block| block.call end result = @pen.write_block do string end assert_equal string, result end def test_spy_hook_records_number_of_calls pen_write_spy = spy_on(@pen, :write) assert_equal 0, pen_write_spy.calls.size 5.times do |i| @pen.write("hello world") assert_equal i + 1, pen_write_spy.calls.size end end def test_has_been_called_with? pen_write_spy = spy_on(@pen, :write) refute pen_write_spy.has_been_called_with?("hello") @pen.write("hello") assert pen_write_spy.has_been_called_with?("hello") @pen.write("world") assert pen_write_spy.has_been_called_with?("hello") @pen.write("hello world") assert pen_write_spy.has_been_called_with?("hello") end def test_spy_hook_records_number_of_calls args = ["hello world"] block = Proc.new {} pen_write_spy = spy_on(@pen, :write) called_from = "#{__FILE__}:#{__LINE__ + 1}:in `#{__method__}'" @pen.write(*args, &block) call_log = pen_write_spy.calls.first assert_equal @pen, call_log.object assert_equal args, call_log.args assert_equal block, call_log.block assert_equal called_from, call_log.called_from end def test_that_method_spy_keeps_arity spy_on(@pen, :write) @pen.write("hello world") assert_raises ArgumentError do @pen.write("hello", "world") end spy_on(@pen, :write_hello) @pen.write_hello assert_raises ArgumentError do @pen.write_hello("hello") end spy_on(@pen, :write_array) @pen.write_hello assert_raises ArgumentError do @pen.write_hello("hello") end spy_on(@pen, :greet) @pen.greet("bob") assert_raises ArgumentError do @pen.greet end assert_raises ArgumentError do @pen.greet("hello", "bob", "error") end end def test_hook_mimics_public_visibility spy_on(@pen, :public_method) assert @pen.singleton_class.public_method_defined? :public_method end def test_hook_mimics_protected_visibility spy_on(@pen, :protected_method) assert @pen.singleton_class.protected_method_defined? :protected_method end def test_hook_mimics_private_visibility spy_on(@pen, :private_method) assert @pen.singleton_class.private_method_defined? :private_method end def test_hook_mimics_class_public_visibility spy_on(Pen, :public_method) assert Pen.public_method_defined? :public_method Spy.off(Pen, :public_method) assert Pen.public_method_defined? :public_method end def test_hook_mimics_class_protected_visibility spy_on(Pen, :protected_method) assert Pen.protected_method_defined? :protected_method Spy.off(Pen, :protected_method) assert Pen.protected_method_defined? :protected_method end def test_hook_mimics_class_private_visibility spy_on(Pen, :private_method) assert Pen.private_method_defined? :private_method Spy.off(Pen, :private_method) assert Pen.private_method_defined? :private_method end def test_spy_get_can_retrieve_a_spy pen_write_spy = spy_on(@pen, :write).and_return(:hello) assert_equal :hello, @pen.write(:world) assert Subroutine.get(@pen, :write).has_been_called? assert_same pen_write_spy, Subroutine.get(@pen, :write) end def test_spy_hook_raises_an_error_on_an_already_hooked_method spy_on(@pen, :write) assert_raises AlreadyHookedError do spy_on(@pen, :write) end end end end spy-0.4.3/test/spy/test_mock.rb0000644000004100000410000000450412706267032016500 0ustar www-datawww-datarequire 'test_helper' module Spy class TestMock < Minitest::Test class BluePen < Pen def write_hello(other) end end def setup @pen_mock = Mock.new(BluePen) @pen = @pen_mock.new end def teardown Spy::Agency.instance.dissolve! end def test_class_methods assert @pen.kind_of?(BluePen) assert @pen.kind_of?(Pen) assert @pen.is_a?(Pen) assert @pen.is_a?(BluePen) assert @pen.instance_of?(BluePen) assert_equal BluePen, @pen.class end def test_raises_error_on_unstubbed_method assert_raises Spy::NeverHookedError do @pen.write("") end end def test_mimics_visibility assert @pen.singleton_class.public_method_defined? :public_method assert @pen.singleton_class.protected_method_defined? :protected_method assert @pen.singleton_class.private_method_defined? :private_method end def test_that_method_spy_keeps_arity assert_raises ArgumentError do @pen.write end assert_raises ArgumentError do @pen.write("hello", "world") end assert_raises ArgumentError do @pen.write_hello end assert_raises ArgumentError do @pen.greet end assert_raises ArgumentError do @pen.greet("hello", "bob", "error") end end def test_that_and_call_original_works Spy.on(@pen, :another).and_call_through assert_equal "another", @pen.another Spy.off(@pen, :another) assert_raises Spy::NeverHookedError do @pen.another end end def test_mocked_methods pen_methods = Pen.public_instance_methods(false) + Pen.protected_instance_methods(false) + Pen.private_instance_methods(false) pen_methods.delete(:initialize) assert_equal pen_methods.sort, @pen_mock.mocked_methods.sort end buggy_methods = [:tap, :pretty_print_inspect] methods_to_test = Object.instance_methods - buggy_methods methods_to_test.each do |method_name| object_method = Object.instance_method(method_name) if object_method.arity == 0 || (RUBY_ENGINE != "jruby" && object_method.parameters == []) define_method("test_base_class_method_#{method_name}_is_not_stubbed") do @pen_mock.new.send(method_name) end end end end end spy-0.4.3/test/integration/0000755000004100000410000000000012706267032015670 5ustar www-datawww-dataspy-0.4.3/test/integration/test_api.rb0000644000004100000410000000144712706267032020033 0ustar www-datawww-datarequire 'test_helper' class TestApi < Minitest::Test include Spy::API def setup @pen = Pen.new Spy.on(@pen, :write) end def test_assert_received @pen.write(:hello) assert_received(@pen, :write) end def test_assert_received_with @pen.write(:world) assert_received_with(@pen, :write, :world) assert_received_with(@pen, :write) do |call| call.args == [:world] end end def test_have_received @pen.write(:foo) matcher = have_received(:write) assert matcher.matches?(@pen) end def test_have_received_with @pen.write(:bar) matcher = have_received(:write).with(:bar) assert matcher.matches?(@pen) matcher = have_received(:write).with do |call| call.args == [:bar] end assert matcher.matches?(@pen) end end spy-0.4.3/test/integration/test_subroutine_spying.rb0000644000004100000410000000251512706267032023047 0ustar www-datawww-datarequire 'test_helper' class TestSpy < Minitest::Test def setup @pen = Pen.new end def teardown Spy::Agency.instance.dissolve! end def test_spy_on_hooks_and_saves_spy_with_array pen_write_spy, pen_write_hello_spy = Spy.on(@pen, :write, :write_hello) assert_nil @pen.write("hello") assert_nil @pen.write_hello assert_kind_of Spy::Subroutine, pen_write_spy assert_kind_of Spy::Subroutine, pen_write_hello_spy assert_equal [pen_write_spy, pen_write_hello_spy], Spy::Agency.instance.subroutines assert pen_write_spy.has_been_called? assert pen_write_hello_spy.has_been_called? end def test_spy_on_hooks_and_saves_spy_with_array pen_write_spy, pen_write_hello_spy = Spy.on(@pen, write: "hello", write_hello: "world") assert_equal "hello", @pen.write(nil) assert_equal "world", @pen.write_hello assert_kind_of Spy::Subroutine, pen_write_spy assert_kind_of Spy::Subroutine, pen_write_hello_spy assert_equal [pen_write_spy, pen_write_hello_spy], Spy::Agency.instance.spies assert pen_write_spy.has_been_called? assert pen_write_hello_spy.has_been_called? end def test_spy_off_unhooks_a_method pen_write_spy = Spy.on(@pen, :write) Spy.off(@pen,:write) assert_equal "hello world", @pen.write("hello world") refute pen_write_spy.has_been_called? end end spy-0.4.3/test/integration/test_instance_method.rb0000644000004100000410000000123612706267032022422 0ustar www-datawww-datarequire 'test_helper' class TestAnyInstanceOf < Minitest::Test class Foo def bar "foobar" end end class Bar < Foo def bar super end end def teardown Spy::Agency.instance.dissolve! end def test_it_overides_all_methods assert_equal Foo.new.bar, "foobar" spy = Spy.on_instance_method(Foo, bar: "timshel") assert_equal spy, Spy::Subroutine.get(Foo, :bar, false) assert_equal "timshel", Foo.new.bar assert_equal "timshel", Foo.new.bar assert_equal "timshel", Bar.new.bar assert_equal 3, spy.calls.size spy = Spy.off_instance_method(Foo, :bar) assert_equal Foo.new.bar, "foobar" end end spy-0.4.3/test/integration/test_mocking.rb0000644000004100000410000000107612706267032020707 0ustar www-datawww-datarequire 'test_helper' class TestMocking < Minitest::Test def teardown Spy::Agency.instance.dissolve! end def test_spy_on_mock_does_not_raise mock = Spy.mock(Pen) spy = Spy.on(mock, :write).and_return(:awesome) assert_equal :awesome, mock.write("hello") assert spy.has_been_called? end def test_spy_mock_shortcuts mock = Spy.mock(Pen, :another, write_hello: :goodbye) assert_nil mock.another assert_equal :goodbye, mock.write_hello end def test_spy_mock_all mock = Spy.mock_all(Pen) assert_nil mock.another end end spy-0.4.3/test/integration/test_constant_spying.rb0000644000004100000410000000223612706267032022501 0ustar www-datawww-datarequire 'test_helper' class TestConstantSpying < Minitest::Test class Foo HELLO = "hello world" def self.hello HELLO end module Bar def self.hello HELLO end end end class ChildFoo < Foo def self.hello HELLO end end def teardown Spy::Agency.instance.dissolve! end def test_spy_on_constant assert_equal "hello world", Foo.hello spy = Spy.on_const(Foo, :HELLO) assert_equal nil, Foo.hello spy.and_return("awesome") assert_equal "awesome", Foo.hello Spy.off_const(Foo, :HELLO) assert_equal "hello world", Foo.hello assert_equal "hello world", Foo::Bar.hello spy = Spy.on_const(Foo, :HELLO) assert_equal nil, Foo::Bar.hello spy.and_return("awesome") assert_equal "awesome", Foo::Bar.hello Spy.off_const(Foo, :HELLO) assert_equal "hello world", Foo::Bar.hello assert_equal "hello world", ChildFoo.hello spy = Spy.on_const(Foo, :HELLO) assert_equal nil, ChildFoo.hello spy.and_return("awesome") assert_equal "awesome", ChildFoo.hello Spy.off_const(Foo, :HELLO) assert_equal "hello world", ChildFoo.hello end end spy-0.4.3/test/support/0000755000004100000410000000000012706267032015061 5ustar www-datawww-dataspy-0.4.3/test/support/pen.rb0000644000004100000410000000165112706267032016173 0ustar www-datawww-dataclass Pen attr_reader :written, :color def initialize(color = :black) @color = color @written = [] end def write(string) @written << string string end def write_block(&block) string = yield @written << string string end def write_hello write("hello") end def write_array(*args) args.each do |arg| write(arg) end end def greet(hello = "hello", name) write("#{hello} #{name}") end def public_method end def another "another" end protected def protected_method end private def private_method end class << self def another "another" end def public_method end protected def protected_method end private def private_method end end end another = "meta_method" Pen.define_singleton_method(:meta_method) do another end Pen.send(:define_method, :meta_method) do another end spy-0.4.3/.gitignore0000644000004100000410000000024612706267032014360 0ustar www-datawww-data*.gem *.rbc *.sw* *.rbx .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp spy-0.4.3/.yardopts0000644000004100000410000000002212706267032014226 0ustar www-datawww-data--markup=markdown spy-0.4.3/CHANGELOG.md0000644000004100000410000000307412706267032014203 0ustar www-datawww-data## Spy 0.4.3 (April 14, 2016) ## * Double performance of spy lookups (@BlakeMesdag) ## Spy 0.4.2 ## * Support for minitest 5.1 ## Spy 0.4.1 ## * Support for minitest 5.0 ## Spy 0.4.0 (May 8, 2013) ## * Allow `Spy#have_received_with` to accept a block * Add automatic test integration for TestUnit, Minitest and Rspec * Fix a few rubinius tests ## Spy 0.3.1 (March 13, 2013) ## * Fix Agency recruiting of mock ## Spy 0.3.0 (March 12, 2013) ## * Added Mock Example: book = Spy.mock(Book, author: "John Steinbeck") * Deprecate Double and use Mock instead * Fix Exceptions so they can have custom messages ## Spy 0.2.5 (March 9, 2013) ## * Add custom exception classes ## Spy 0.2.4 (February 28, 2013) ## * Fix airty checking of Spy::Subroutine#and_return ## Spy 0.2.3 (February 28, 2013) ## * Fix marshal dumping * Add Docs * Make error messages clearer ## Spy 0.2.2 (February 26, 2013) ## * Make compatible with ruby 2.0.0 ## Spy 0.2.1 (February 25, 2013) ## * fix missing CallLog ## Spy 0.2.0 (February 22, 2013) ## * Add CallLog * Fix constant stubbing * Ensure spy is logging all calls even if an error is raised * add Spy::Subroutine#called_with ## Spy 0.1.0 (February 20, 2013) ## * add Spy#and_raise * add Spy#and_throw * add Spy#and_yield * add Constant stubbing * fix private method lookups ## Spy 0.0.1 (February 5, 2013) ## * Stub objects Example: Spy.on(book, :title).and_return("East of Eden") * Create Doubles Example: Spy.double("Double Name", stub: :method) spy-0.4.3/README.md0000644000004100000410000001553412706267032013655 0ustar www-datawww-data# Spy [![Gem Version](https://badge.fury.io/rb/spy.png)](http://badge.fury.io/rb/spy) [![Build Status](https://travis-ci.org/ryanong/spy.png?branch=master)](https://travis-ci.org/ryanong/spy) [![Coverage Status](https://coveralls.io/repos/ryanong/spy/badge.png?branch=master)](https://coveralls.io/r/ryanong/spy) [Docs](http://rdoc.info/gems/spy/frames) Spy is a lightweight stubbing framework with support for method spies, constant stubs, and object mocks. Spy was designed for 1.9.3+. Spy features that were completed were tested against the rspec-mocks tests so it covers all cases that rspec-mocks does. Inspired by the spy api of the jasmine javascript testing framework. ## Why use this instead of rspec-mocks, mocha, or etc * Spy will raise error when you try to stub/spy a method that doesn't exist * when you change your method name your unit tests will break * no more fantasy tests * Spy arity matches original method * Your tests will raise an error if you use the wrong arity * Spy visibility matches original method * Your tests will raise an error if you try to call the method incorrectly * Simple call log api * easier to read tests * use ruby to test ruby instead of a dsl * no expectations * really who thought that was a good idea? * absolutely no polution of global object space * no polution of instance variables for stubbed objects Fail faster, code faster. ## Why not to use this * mocking null objects is not supported(yet) * no argument matchers for `Spy::Subroutine#has_been_called_with` * cannot watch all calls to an object to check order in which they are called * cannot transfer nested constants when stubbing a constant * i don't think anybody uses this anyway * nobody on github does * #with is not supported * you can usually just check the call logs. * if you do need to use this. It is probably a code smell. You either need to abstract your method more or add separate tests. * you want to use dumb double, Spy has smart mocks, they are better * you use `mock_model` and `stub_model` (I want to impliment this soon) ## Installation Add this line to your application's Gemfile: gem 'spy' And then execute: $ bundle Or install it yourself as: $ gem install spy ## Usage ### Method Stubs A method stub overrides a pre-existing method and records all calls to specified method. You can set the spy to return either the original method or your own custom implementation. Spy support 2 different ways of spying an existing method on an object. ```ruby Spy.on(book, title: "East of Eden") Spy.on(book, :title).and_return("East of Eden") Spy.on(book, :title).and_return { "East of Eden" } book.title #=> "East of Eden" ``` Spy will raise an error if you try to stub on a method that doesn't exist. You can force the creation of a stub on method that didn't exist but it really isn't suggested. ```ruby Spy::Subroutine.new(book, :flamethrower).hook(force:true).and_return("burnninante") ``` You can also stub instance methods of Classes and Modules. This is equivalent to rspec-mock's `Module#any_instance` ```ruby Spy.on_instance_method(Book, :title).and_return("Cannery Row") Book.new(title: "Siddhartha").title #=> "Cannery Row" Book.new(title: "The Big Cheese").title #=> "Cannery Row" ``` ### Test Mocks A test mock is an object that quacks like a given class but will raise an error when the method is not stubbed. Spy will not let you stub a method that wasn't on the mocked class. You can spy on the classes and call through to the original method. ```ruby book = Spy.mock(Book) # Must be a class Spy.on(book, first_name: "Neil", last_name: "Gaiman") Spy.on(book, :author).and_call_through book.author #=> "Neil Gaiman" book.responds_to? :title #=> true book.title #=> Spy::NeverHookedError: 'title' was never hooked on mock spy. ``` To stub methods during instantiation just add arguments. ```ruby book = Spy.mock(Book, :first_name, author: "Neil Gaiman") ``` ### Arbitrary Handling If you need to have a custom method based in the method inputs just send a block to `#and_return` ```ruby Spy.on(book, :read_page).and_return do |page, &block| block.call "awesome " * page end ``` An error will raise if the arity of the block is larger than the arity of the original method. However this can be overidden with the force argument. ```ruby Spy.on(book, :read_page).and_return(force: true) do |a, b, c, d| end ``` ### Method Spies When you stub a method it returns a spy. A spy records what calls have been made to a given method. ```ruby validator = Spy.mock(Validator) validate_spy = Spy.on(validator, :validate) validate_spy.has_been_called? #=> false validator.validate("01234") #=> nil validate_spy.has_been_called? #=> true validate_spy.has_been_called_with?("01234") #=> true ``` You can also retrieve a method spy on demand ```ruby Spy.get(validator, :validate) ``` ### Calling through If you just want to make sure if a method is called and not override the output you can just use the `#and_call_through` method ```ruby Spy.on(book, :read_page).and_call_through ``` By if the original method never existed it will call `#method_missing` on the spied object. ### Call Logs When a spy is called on it records a call log. A call log contains the object it was called on, the arguments and block that were sent to method and what it returned. ```ruby read_page_spy = Spy.on(book, read_page: "hello world") book.read_page(5) { "this is a block" } book.read_page(3) book.read_page(7) read_page_spy.calls.size #=> 3 first_call = read_page_spy.calls.first first_call.object #=> book first_call.args #=> [5] first_call.block #=> Proc.new { "this is a block" } first_call.result #=> "hello world" first_call.called_from #=> "file_name.rb:line_number" ``` ## Test Framework Integration ### MiniTest/TestUnit in your `test_helper.rb` add this line after you include your framework ```ruby require 'spy/integration' ``` In your test file ```ruby def test_title book = Book.new title_spy = Spy.on(book, title) book.title book.title assert_received book, :title assert title_spy.has_been_called? assert_equal 2, title_spy.calls.count end ``` ### Rspec In `spec_helper.rb` ```ruby require "rspec/autorun" require "spy/integration" RSpec.configure do |c| c.mock_with Spy::RspecAdapter end ``` In your test ```ruby describe Book do it "title can be called" do book = book.new page_spy = Spy.on(book, page) book.page(1) book.page(2) expect(book).to have_received(:page) expect(book).to have_received(:page).with(1) expect(book).to have_received(:page).with(2) expect(page_spy).to have_been_called expect(page_spy.calls.count).to eq(2) end end ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request