moses-rspec-spies 3.0.0rc

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gem 'rspec', '~> 2.0'
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ gem 'jeweler'
8
+ gem 'rdoc'
9
+ end
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.2)
11
+ rdoc (3.6.1)
12
+ rspec (2.8.0)
13
+ rspec-core (~> 2.8.0)
14
+ rspec-expectations (~> 2.8.0)
15
+ rspec-mocks (~> 2.8.0)
16
+ rspec-core (2.8.0)
17
+ rspec-expectations (2.8.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.8.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ jeweler
26
+ rake
27
+ rdoc
28
+ rspec (~> 2.0)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Joshua Nichols
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,117 @@
1
+ = rspec-spies
2
+
3
+ Behold, the Test Spies pattern! http://xunitpatterns.com/Test%20Spy.html
4
+
5
+ Some other mocking frameworks support this. In particular, rr, notamock, and jferris's fork of mocha. It'd be great to see support for it in rspec's mocking library as well.
6
+
7
+ Why not just switch to another framework? Well, because migration is a huge hassle when you have a sizeable number of specs, in part to subtle varations in the framework. Even something like notamock that was meant to work with rspec out of the box... has its differences. Therefore, using the existing rspec mocking framework, with a small enhancements, is pretty idyllic.
8
+
9
+ In general, I think it should work by doing your usual stub!, followed running the code under test, and then verifying with a matcher. The matcher should probably be in line with the syntax of mocking with should_receive. Here's a short example:
10
+
11
+
12
+ describe "index" do
13
+ before do
14
+ @post = stub_model(Post)
15
+ Post.stub!(:find).and_return(@post)
16
+ get :show, :id => @post.id
17
+ end
18
+
19
+ it "should find post by id" do
20
+ Post.should have_received(:find).with(@post.id)
21
+ end
22
+ end
23
+
24
+ To get started, you just need to:
25
+
26
+ * Run: gem install rspec-spies --source http://gemcutter.org
27
+ * Update spec_helper to include this line: require 'rspec-spies'
28
+ * Alternatively, you can add `-r rspec-spies` to your project's .rspec file
29
+
30
+
31
+ == Before and after test spies
32
+
33
+ If you think this simple case is redudant, consider this extended example without using test spies (note: these are using shoulda's rspec matchers):
34
+
35
+ describe "show" do
36
+ before do
37
+ @post = stub_model(Post)
38
+ # always stub find, so most of the specifications will work
39
+ # alternatively, this could be in each 'it' block that doesn't have a should_receive
40
+ Post.stub!(:find).and_return(@post)
41
+ end
42
+
43
+ it "should respond with success" do
44
+ get :show, :id => @post.id
45
+
46
+ controller.should respond_with(:success)
47
+ end
48
+
49
+ it "should render show template" do
50
+ get :show, :id => @post.id
51
+ controller.should render_template(:show)
52
+ end
53
+
54
+ it "should find the post by id" do
55
+ Post.should_receive(:find).with(@post.id).and_return(@post)
56
+ # note we have to specify the return value again
57
+ # also, this expectation is set BEFORE we actually do anything
58
+ get :show, :id => @post.id
59
+ end
60
+
61
+ it "should assign post" do
62
+ get :show, :id
63
+
64
+ controller.should assign_to(:post).with(@post)
65
+ end
66
+
67
+ end
68
+
69
+ The problems here:
70
+
71
+ * Running get in each it block. This is because we want to be able to use a mock expectation (ie @Post.should_receive) in one of the specifications. The should_receive could be in the setup instead of @stub!@, but if find is not called, it'll make all the specs fail, instead of just the one expecting it to be called.
72
+ * We're stubbing it once in the setup, and then mocking the same method in a later. This means we have to specify the return value both in the setup, so other specs work, and then again in the 'it' block with the mocking so that one will work.
73
+
74
+ Contrast this with using test spies for this same situation:
75
+
76
+ describe "show" do
77
+ before do
78
+ @post = stub_model(Post)
79
+ Post.stub!(:find).and_return(@post)
80
+
81
+ get :show, :id => @post.id
82
+ end
83
+
84
+ it { should respond_with(:success) }
85
+
86
+ it { should render_template(:show) }
87
+
88
+ it { should assign_to(:post).with(@post) }
89
+
90
+ it "should find the post by id" do
91
+ Post.should have_received(:find).with(@post.id)
92
+ end
93
+ end
94
+
95
+ Note here that we only do a get once, and that we only have to specify the return value of @find once in the setup block.
96
+
97
+ When I came on board the project I'm currently on (written by people who I want to think were pretty good at rspec), it was entirely in the style of the first example whenever mocking was used. This is a pretty simple case, but you could imagine it getting out of control if you are mocking more than just a single method. As a result, it took me a lot longer than it should have to fully understand what was going on, and it took longer to add more specs than it should have.
98
+
99
+ After writing up the test spies, I've been rewriting the specs when I return to them to write them in the style of the second example, and it definitely feels a lot better.
100
+
101
+ Lastly, I'd just want to make it clear that I'm not suggesting we stop using the first style. I just want the option of using test spies. The way this patch is written, I don't see it interfering with the existing style.
102
+
103
+
104
+ == Note on Patches/Pull Requests
105
+
106
+ * Fork the project.
107
+ * Make your feature addition or bug fix.
108
+ * Add tests for it. This is important so I don't break it in a
109
+ future version unintentionally.
110
+ * Commit, do not mess with rakefile, version, or history.
111
+ (if you want to have your own version, that is fine but
112
+ bump version in a commit by itself I can ignore when I pull)
113
+ * Send me a pull request. Bonus points for topic branches.
114
+
115
+ == Copyright
116
+
117
+ Copyright (c) 2009 Joshua Nichols. See LICENSE for details.
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup(:default, :development)
4
+
5
+ require 'rake'
6
+
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "moses-rspec-spies"
10
+ gem.summary = %Q{rspec has gone without test spies. no more!}
11
+ gem.version = File.exist?('VERSION') ? File.read('VERSION') : ""
12
+ gem.description = %Q{test spies, for rspec (forked from technicalpickles/rspec-spies)}
13
+ gem.email = "moses@moseshohman.com"
14
+ gem.homepage = "http://github.com/moses/rspec-spies"
15
+ gem.authors = ["Joshua Nichols", "Moses Hohman"]
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+
20
+ require 'rspec/core/rake_task'
21
+ RSpec::Core::RakeTask.new do |t|
22
+ t.pattern = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ RSpec::Core::RakeTask.new(:rcov) do |t|
26
+ t.pattern = FileList['spec/**/*_spec.rb']
27
+ t.rcov = true
28
+ t.rcov_opts = ['--exclude', 'spec']
29
+ end
30
+
31
+ task :default => :spec
32
+
33
+ require 'rdoc/task'
34
+ RDoc::Task.new do |rdoc|
35
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
36
+
37
+ rdoc.rdoc_dir = 'rdoc'
38
+ rdoc.title = "rspec-spies #{version}"
39
+ rdoc.rdoc_files.include('README*')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 3.0.0rc
@@ -0,0 +1,50 @@
1
+ require 'rspec/mocks/method_double'
2
+ RSpec::Mocks::MethodDouble.class_eval do
3
+ # override defining stubs to record the message was called.
4
+ # there's only one line difference from upstream rspec, but can't change it without fully overriding
5
+ def define_proxy_method
6
+ method_name = @method_name
7
+ visibility_for_method = "#{visibility} :#{method_name}"
8
+ object_singleton_class.class_eval(<<-EOF, __FILE__, __LINE__)
9
+ def #{method_name}(*args, &block)
10
+ __mock_proxy.record_message_received :#{method_name}, *args, &block
11
+ __mock_proxy.message_received :#{method_name}, *args, &block
12
+ end
13
+ #{visibility_for_method}
14
+ EOF
15
+ end
16
+ end
17
+
18
+ require 'rspec/expectations'
19
+ require 'rspec/matchers'
20
+ RSpec::Matchers.define :have_received do |method_name, args, block|
21
+ match do |actual|
22
+ messages_received = actual.send(:__mock_proxy).instance_variable_get("@messages_received")
23
+ messages_received.any? do |message|
24
+ received_method_name, received_args, received_block = *message
25
+ result = (received_method_name == method_name)
26
+ result &&= RSpec::Mocks::ArgumentExpectation.new(@args || any_args).args_match?(received_args)
27
+ result &&= (received_block == block)
28
+ end
29
+ end
30
+
31
+ chain :with do |*args|
32
+ @args = args
33
+ end
34
+
35
+ failure_message_for_should do |actual|
36
+ "expected #{actual.inspect} to have received #{method_name.inspect}#{args_message}"
37
+ end
38
+
39
+ failure_message_for_should_not do |actual|
40
+ "expected #{actual.inspect} to not have received #{method_name.inspect}#{args_message}, but did"
41
+ end
42
+
43
+ description do
44
+ "to have received #{method_name.inspect}#{args_message}"
45
+ end
46
+
47
+ def args_message
48
+ @args ? " with #{@args.inspect}" : ""
49
+ end
50
+ end
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "moses-rspec-spies"
8
+ s.version = "3.0.0rc"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Joshua Nichols", "Moses Hohman"]
12
+ s.date = "2012-04-08"
13
+ s.description = "test spies, for rspec (forked from technicalpickles/rspec-spies)"
14
+ s.email = "moses@moseshohman.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/rspec-spies.rb",
28
+ "moses-rspec-spies.gemspec",
29
+ "spec/rspec-spies_spec.rb",
30
+ "spec/spec.opts",
31
+ "spec/spec_helper.rb"
32
+ ]
33
+ s.homepage = "http://github.com/moses/rspec-spies"
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = "1.8.11"
36
+ s.summary = "rspec has gone without test spies. no more!"
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_runtime_dependency(%q<rspec>, ["~> 2.0"])
43
+ s.add_development_dependency(%q<rake>, [">= 0"])
44
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
45
+ s.add_development_dependency(%q<rdoc>, [">= 0"])
46
+ else
47
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
48
+ s.add_dependency(%q<rake>, [">= 0"])
49
+ s.add_dependency(%q<jeweler>, [">= 0"])
50
+ s.add_dependency(%q<rdoc>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
54
+ s.add_dependency(%q<rake>, [">= 0"])
55
+ s.add_dependency(%q<jeweler>, [">= 0"])
56
+ s.add_dependency(%q<rdoc>, [">= 0"])
57
+ end
58
+ end
59
+
@@ -0,0 +1,75 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Spec
4
+ module Matchers
5
+ describe "[object.should] have_received(method, *args)" do
6
+ before do
7
+ @object = String.new("HI!")
8
+ end
9
+
10
+ it "matches if method is called with correct args" do
11
+ @object.stub!(:slice)
12
+ @object.slice(5)
13
+
14
+ have_received(:slice).with(5).matches?(@object).should be_true
15
+ end
16
+
17
+ it "matches if doesn't specify args, even if method is called with args" do
18
+ @object.stub!(:slice)
19
+ @object.slice(5)
20
+
21
+ have_received(:slice).matches?(@object).should be_true
22
+ end
23
+
24
+ it "matches if specifies nil arg, if method is called with a nil arg" do
25
+ @object.stub!(:slice)
26
+ @object.slice(nil)
27
+
28
+ have_received(:slice).with(nil).matches?(@object).should be_true
29
+ have_received(:slice).matches?(@object).should be_true
30
+ end
31
+
32
+ it "does not match if method is called with incorrect args" do
33
+ @object.stub!(:slice)
34
+ @object.slice(3)
35
+
36
+ have_received(:slice).with(5).matches?(@object).should be_false
37
+ end
38
+
39
+ it "does not match if method is not called" do
40
+ @object.stub!(:slice)
41
+
42
+ have_received(:slice).with(5).matches?(@object).should be_false
43
+ end
44
+
45
+ it "correctly lists expects arguments for should" do
46
+ @object.stub!(:slice)
47
+
48
+ matcher = have_received(:slice).with(5, 3)
49
+ messages = matcher.instance_variable_get("@messages")
50
+ message = messages[:failure_message_for_should].call(@object)
51
+ message.should == "expected \"HI!\" to have received :slice with [5, 3]"
52
+ end
53
+
54
+ it "doesn't show nil for arguments in description" do
55
+ @object.stub!(:slice)
56
+
57
+ @object.slice(3)
58
+ matcher = have_received(:slice).with(3)
59
+ messages = matcher.instance_variable_get("@messages")
60
+ message = messages[:description].call(@object)
61
+ message.should_not include "nil"
62
+ end
63
+
64
+ it "correctly lists expects arguments for should_not" do
65
+ @object.stub!(:slice)
66
+
67
+ matcher = have_received(:slice).with(1, 2)
68
+ messages = matcher.instance_variable_get("@messages")
69
+ message = messages[:failure_message_for_should_not].call(@object)
70
+ message.should == "expected \"HI!\" to not have received :slice with [1, 2], but did"
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rspec'
4
+ require 'rspec/autorun'
5
+
6
+ require 'rspec-spies'
7
+
8
+ RSpec.configure do |config|
9
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moses-rspec-spies
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: 5
5
+ version: 3.0.0rc
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Nichols
9
+ - Moses Hohman
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2012-04-08 00:00:00 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: "2.0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: jeweler
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rdoc
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ description: test spies, for rspec (forked from technicalpickles/rspec-spies)
61
+ email: moses@moseshohman.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - LICENSE
68
+ - README.rdoc
69
+ files:
70
+ - .document
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - LICENSE
74
+ - README.rdoc
75
+ - Rakefile
76
+ - VERSION
77
+ - lib/rspec-spies.rb
78
+ - moses-rspec-spies.gemspec
79
+ - spec/rspec-spies_spec.rb
80
+ - spec/spec.opts
81
+ - spec/spec_helper.rb
82
+ homepage: http://github.com/moses/rspec-spies
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">"
100
+ - !ruby/object:Gem::Version
101
+ version: 1.3.1
102
+ requirements: []
103
+
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.11
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: rspec has gone without test spies. no more!
109
+ test_files: []
110
+