benhutton-remarkable_rails 4.0.0.alpha4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG ADDED
@@ -0,0 +1,105 @@
1
+ * Allow should route(:get, :action => :show, :id => 1).to("/projects/1") and
2
+ should route(:get, "/projects/1").from(:action => :show, :id => 1) syntax.
3
+
4
+ * Plugin files are automatically loaded [#86]
5
+
6
+ * Allow should_route to use environment configuration [#88] (thanks to Lawrence Pit)
7
+
8
+ * Allow mock model class to be set using :as option.
9
+
10
+ * [DEPRECATION] By default all matchers perform expectations, use with_stubs => true
11
+ if you want otherwise.
12
+
13
+ * [DEPRECATION] mock_models now creates model_proc instead of mock_model.
14
+ Altough this won't fire any deprecation warning, all the documentation was changed.
15
+
16
+ * assert_valid_keys on expects
17
+
18
+ * mock_models now creates a second class method to be used on the index action [#71]
19
+ In other words, mock_models :project will create:
20
+
21
+ def self.mock_project
22
+ proc { mock_project }
23
+ end
24
+
25
+ # This was added to be used on index actions
26
+ def self.mock_projects
27
+ proc { [ mock_project ] }
28
+ end
29
+
30
+ def mock_project(stubs={})
31
+ @project ||= mock_model(Project, stubs)
32
+ end
33
+
34
+ * Allow multiple args to be given to :with in expects. If you need to verify that
35
+ an array is being sent, you need to send an array inside another array [#70]
36
+
37
+ * Allow procs or blocks to be given to respond_with_body and respond_with :body [#67]
38
+
39
+ * Allow ordered to be given to macro stubs as option [#66]
40
+
41
+ # v3.1
42
+
43
+ * Ensure set_cookies and set_session work with arrays [#55]
44
+
45
+ * Added set_cookies matcher [#51]
46
+
47
+ * Add a helper to declare that a XmlHttpRequest should be performed:
48
+
49
+ describe :get => :show do
50
+ xhr!
51
+
52
+ * Macro stubs now supports blocks too [#50]
53
+
54
+ expects :human_attribute_name, :on => Project, :with => :title do |attr|
55
+ attr.to_s.humanize
56
+ end
57
+
58
+ * :to option in set_session and set_the_flash now accepts Regexp [#46]
59
+
60
+ * render_template now works with partials [#43]
61
+
62
+ * Added to support for routing example group (inside spec/routing) [#26]
63
+
64
+ # v3.0
65
+
66
+ * redirect_to and render_template were ported from rspec-rails to
67
+ remarkable to provide I18n. The second was also extended to deal with :with,
68
+ :layout and :content_type as options.
69
+
70
+ render_with_layout, render_without_layout delegate their logic to render_template
71
+ so they share the same options.
72
+
73
+ respond_with_content_type and respond_wity_body delegate their logic to
74
+ respond_with matcher, so they also share the same options.
75
+
76
+ :set_the_flash was also redesign to inherit from :set_session, providing a
77
+ consistent API.
78
+
79
+ * remarkable_rails now ships with a new feature, called macro stubs.
80
+ This allows you to declare just once your mocks and/or expectations, and each
81
+ matcher will know how to deal with properly. A TasksController could have your
82
+ specs for a create action rewritten like this:
83
+
84
+ describe TasksController do
85
+ mock_models :task
86
+
87
+ describe :post => :create, :task => { :these => 'params' } do
88
+ expects :new, :on => Task, with => {'these' => 'params'}, :returns => task_proc
89
+ expects :save, :on => mock_task, :returns => true
90
+
91
+ should_assign_to :task, :with => task_proc
92
+ should_redirect_to { task_url(mock_task) }
93
+ end
94
+ end
95
+
96
+ It automatically performs the action before running each macro. In assign_to,
97
+ it executes the expects as expectations (:should_receive), and in redirect_to
98
+ it executes the expects as stubs (:stub!), just as above.
99
+
100
+ For more options, information and configuration, check macro stubs documentation.
101
+
102
+ # v2.x
103
+
104
+ * Added assign_to, filter_params, render_with_layout, respond_with
105
+ respond_with_content_type, route, set_session and set_the_flash matchers.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Carlos Brando
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.
data/README ADDED
@@ -0,0 +1,116 @@
1
+ = Remarkable Rails
2
+
3
+ Remarkable Rails is a collection of matchers to Rails. This package has some
4
+ ActionController matchers and soon some ActionView matchers.
5
+
6
+ Whenever using the Remarkable Rails gem, it will automatically add your ActiveRecord
7
+ matchers. So just one line is needed to install both:
8
+
9
+ sudo gem install remarkable_rails
10
+
11
+ If you are using Rails 2.3, you need to have this configuration on your
12
+ config/environments/test.rb:
13
+
14
+ config.gem "rspec", :lib => false
15
+ config.gem "rspec-rails", :lib => false
16
+ config.gem "remarkable_rails", :lib => false
17
+
18
+ And then require remarkable inside your spec_helper.rb, after "spec/rails":
19
+
20
+ require 'spec/rails'
21
+ require 'remarkable_rails'
22
+
23
+ == Matchers & Macros
24
+
25
+ The supported matchers and macros are:
26
+
27
+ assign_to, filter_params, render_with_layout, respond_with,
28
+ respond_with_content_type, route, set_session and set_the_flash matchers.
29
+
30
+ In Remarkable 3.0, we also ported and extended redirect to and render template
31
+ from rspec rails matchers to provide I18n. You can also do:
32
+
33
+ render_template 'edit', :layout => 'default'
34
+ respond_with 404, :content_type => Mime::XML, :body => /Not found/
35
+
36
+ == Macro stubs
37
+
38
+ Another cool feature in Remarkable 3.0 is macro stubs, which makes your mocks
39
+ and stubs DRY and easier to maintain. An rspec default scaffold would be:
40
+
41
+ describe TasksController do
42
+ def mock_task(stubs={})
43
+ @task ||= mock_model(Task, stubs)
44
+ end
45
+
46
+ describe “responding to #POST create” do
47
+ it "exposes a newly created task as @task" do
48
+ Task.should_receive(:new).with({'these' => 'params'}).
49
+ and_return(mock_task(:save => true))
50
+ post :create, :task => {:these => 'params'}
51
+ assigns[:task].should equal(mock_task)
52
+ end
53
+
54
+ it "redirects to the created task" do
55
+ Task.stub!(:new).and_return(mock_task(:save => true))
56
+ post :create, :task => {}
57
+ response.should redirect_to(task_url(mock_task))
58
+ end
59
+ end
60
+ end
61
+
62
+ An equivalent in remarkable would be:
63
+
64
+ describe TasksController do
65
+ mock_models :task
66
+
67
+ describe :post => :create, :task => { :these => 'params' } do
68
+ expects :new, :on => Task, :with => {'these' => 'params'}, :returns => task_proc
69
+ expects :save, :on => task_proc, :returns => true
70
+
71
+ should_assign_to :task, :with => task_proc
72
+ should_redirect_to { task_url(mock_task) }
73
+ end
74
+ end
75
+
76
+ It automatically performs the action before running each macro. It executes the
77
+ expects as expectations (:should_receive), but you can supply :with_stubs => true
78
+ if you want it to be executed with stubs.
79
+
80
+ There are also params and mime methods:
81
+
82
+ describe TasksController
83
+ params :project_id => 42
84
+ mime Mime::HTML
85
+
86
+ describe :get => :show, :id => 37 do
87
+ should_assign_to :project, :task
88
+
89
+ describe Mime::XML do
90
+ should_assign_to :project, :task
91
+ end
92
+ end
93
+ end
94
+
95
+ And much more. Be sure to check macro stubs documentation.
96
+
97
+ == Rails plugin developers
98
+
99
+ Remarkable automatically loads files at the remarkable directory on your plugins
100
+ and frozen gems if RAILS_ROOT is defined. The lookup happens like this:
101
+
102
+ RAILS_ROOT/spec/remarkable
103
+ RAILS_ROOT/vendor/gems/*/remarkable
104
+ RAILS_ROOT/vendor/plugins/*/remarkable
105
+
106
+ Remarkable will load both ruby files (.rb) and Remarkable locale files (.yml).
107
+
108
+ The only step remaining is to include the matchers, which Remarkable will not
109
+ do automatically if the user is not using a Remarkable namespace. For example,
110
+ if the developer includes his matchers to Remarkable::ActiveRecord::Matchers,
111
+ the matchers will be automatically available in users spec. But if he creates
112
+ a new namespace, like MyPlugin::Matchers, he has to tell Remarkable to include
113
+ them in the proper example group:
114
+
115
+ Remarkable.include_matchers!(MyPlugin::Matchers, Spec::Rails::Example::ModelExampleGroup)
116
+
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{benhutton-remarkable_rails}
8
+ s.version = "4.0.0.alpha4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben Hutton", "Carlos Brando", "José Valim"]
12
+ s.date = %q{2010-11-15}
13
+ s.description = %q{Remarkable Rails: collection of matchers and macros with I18n for Rails}
14
+ s.email = ["benhutton@gmail.com", "eduardobrando@gmail.com", "jose.valim@gmail.com"]
15
+ s.extra_rdoc_files = [
16
+ "CHANGELOG",
17
+ "LICENSE",
18
+ "README"
19
+ ]
20
+ s.files = [
21
+ "CHANGELOG",
22
+ "LICENSE",
23
+ "README",
24
+ "benhutton-remarkable_rails.gemspec",
25
+ "lib/remarkable_rails.rb",
26
+ "lib/remarkable_rails/action_controller.rb",
27
+ "lib/remarkable_rails/action_controller/base.rb",
28
+ "lib/remarkable_rails/action_controller/macro_stubs.rb",
29
+ "lib/remarkable_rails/action_controller/matchers/assign_to_matcher.rb",
30
+ "lib/remarkable_rails/action_controller/matchers/filter_params_matcher.rb",
31
+ "lib/remarkable_rails/action_controller/matchers/redirect_to_matcher.rb",
32
+ "lib/remarkable_rails/action_controller/matchers/render_template_matcher.rb",
33
+ "lib/remarkable_rails/action_controller/matchers/respond_with_matcher.rb",
34
+ "lib/remarkable_rails/action_controller/matchers/route_matcher.rb",
35
+ "lib/remarkable_rails/action_controller/matchers/set_cookies_matcher.rb",
36
+ "lib/remarkable_rails/action_controller/matchers/set_session_matcher.rb",
37
+ "lib/remarkable_rails/action_controller/matchers/set_the_flash_matcher.rb",
38
+ "lib/remarkable_rails/action_view.rb",
39
+ "lib/remarkable_rails/action_view/base.rb",
40
+ "lib/remarkable_rails/active_orm.rb",
41
+ "locale/en.yml",
42
+ "remarkable_rails.gemspec"
43
+ ]
44
+ s.homepage = %q{http://github.com/benhutton/remarkable}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubyforge_project = %q{remarkable}
48
+ s.rubygems_version = %q{1.3.7}
49
+ s.summary = %q{Remarkable Rails: collection of matchers and macros with I18n for Rails}
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<rspec>, [">= 2.0.0"])
57
+ s.add_runtime_dependency(%q<rspec-rails>, [">= 2.0.0"])
58
+ s.add_runtime_dependency(%q<remarkable>, ["~> 4.0.0.alpha4"])
59
+ s.add_runtime_dependency(%q<remarkable_activerecord>, ["~> 4.0.0.alpha4"])
60
+ else
61
+ s.add_dependency(%q<rspec>, [">= 2.0.0"])
62
+ s.add_dependency(%q<rspec-rails>, [">= 2.0.0"])
63
+ s.add_dependency(%q<remarkable>, ["~> 4.0.0.alpha4"])
64
+ s.add_dependency(%q<remarkable_activerecord>, ["~> 4.0.0.alpha4"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<rspec>, [">= 2.0.0"])
68
+ s.add_dependency(%q<rspec-rails>, [">= 2.0.0"])
69
+ s.add_dependency(%q<remarkable>, ["~> 4.0.0.alpha4"])
70
+ s.add_dependency(%q<remarkable_activerecord>, ["~> 4.0.0.alpha4"])
71
+ end
72
+ end
73
+
@@ -0,0 +1,48 @@
1
+ # Load Remarkable
2
+ require 'remarkable/core'
3
+
4
+ # Load spec/rails
5
+ # require 'rspec/rails'
6
+
7
+ # Load Remarkable Rails base files
8
+ dir = File.dirname(__FILE__)
9
+ require File.join(dir, 'remarkable_rails', 'active_orm')
10
+ require File.join(dir, 'remarkable_rails', 'action_controller')
11
+ require File.join(dir, 'remarkable_rails', 'action_view')
12
+
13
+ # Load locale file
14
+ Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
15
+ # Load plugin files if RAILS_ROOT is defined. It loads files at:
16
+ #
17
+ # RAILS_ROOT/spec/remarkable
18
+ # RAILS_ROOT/vendor/gems/*/remarkable
19
+ # RAILS_ROOT/vendor/plugins/*/remarkable
20
+ #
21
+ # Remarkable will load both ruby files (.rb) and Remarkable locale files (.yml).
22
+ #
23
+ # The only step remaining is to include the matchers, which Remarkable will not
24
+ # do automatically if the user is not using a Remarkable namespace. For example,
25
+ # if the developer includes his matchers to Remarkable::ActiveRecord::Matchers,
26
+ # the matchers will be automatically available in users spec. But if he creates
27
+ # a new namespace, like MyPlugin::Matchers, he has to tell Remarkable to include
28
+ # them in the proper example group:
29
+ #
30
+ # Remarkable.include_matchers!(MyPlugin::Matchers, Spec::Rails::Example::ModelExampleGroup)
31
+ #
32
+ if defined?(Rails.root)
33
+ files = []
34
+ files += Dir.glob(File.join(Rails.root, "spec", "remarkable", "*"))
35
+ files += Dir.glob(File.join(Rails.root, "vendor", "{plugins,gems}", "*", "remarkable", "*"))
36
+ files.each do |file|
37
+ begin
38
+ case File.extname(file)
39
+ when ".rb"
40
+ require file
41
+ when ".yml"
42
+ Remarkable.add_locale file
43
+ end
44
+ rescue Exception => e
45
+ warn "[WARNING] Remarkable could not load file #{file.inspect}. Error: #{e.message}"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ module Remarkable
2
+ module ActionController
3
+ end
4
+ end
5
+
6
+ dir = File.dirname(__FILE__)
7
+ require File.join(dir, 'action_controller', 'base')
8
+ require File.join(dir, 'action_controller', 'macro_stubs')
9
+
10
+ # Load matchers
11
+ Dir[File.join(dir, 'action_controller', 'matchers', '*.rb')].each do |file|
12
+ require file
13
+ end
14
+
15
+ # Include macro_stubs and matchers in Spec::Rails
16
+ if defined?(RSpec::Rails)
17
+ # RSpec::Rails::ControllerExampleGroup.send :include, Remarkable::ActionController::MacroStubs
18
+
19
+ Remarkable.include_matchers!(Remarkable::ActionController, RSpec::Rails::ControllerExampleGroup)
20
+ Remarkable.include_matchers!(Remarkable::ActionController, RSpec::Rails::RoutingExampleGroup)
21
+ end
22
+
@@ -0,0 +1,31 @@
1
+ module Remarkable
2
+ module ActionController
3
+ class Base < Remarkable::Base
4
+
5
+ before_assert :perform_action_with_macro_stubs
6
+
7
+ optional :with_expectations, :default => true
8
+ optional :with_stubs, :default => true
9
+
10
+ protected
11
+
12
+ # Before assertions, call run_action! to perform the action if it was
13
+ # not performed yet.
14
+ #
15
+ def perform_action_with_macro_stubs #:nodoc:
16
+ @spec.send(:run_action!, run_with_expectations?) if @spec.send(:controller)
17
+ end
18
+
19
+ def run_with_expectations? #:nodoc:
20
+ if @options.key?(:with_stubs)
21
+ !@options[:with_stubs]
22
+ elsif @options.key?(:with_expectations)
23
+ @options[:with_expectations]
24
+ else
25
+ true
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,594 @@
1
+ module RSpec
2
+ module Core
3
+
4
+ # This is a hack that we need to run the alias method chain describe
5
+ class Metadata < Hash
6
+ def first_caller_from_outside_rspec_from_caller(list)
7
+ list.detect {|l| l !~ /(\/lib\/rspec\/core)|remarkable_rails/}
8
+ end
9
+ end
10
+
11
+ # Macro stubs makes stubs and expectations easier, more readable and DRY.
12
+ #
13
+ # == Example
14
+ #
15
+ # Let's jump off to an example:
16
+ #
17
+ # describe ProjectsController do
18
+ # describe :get => :show, :id => 37 do
19
+ # expects :find, :on => Project, :with => '37', :returns => proc { mock_project }
20
+ #
21
+ # should_assign_to :project, :with => proc { mock_project }
22
+ # should_render_template 'show'
23
+ #
24
+ # describe Mime::XML do
25
+ # should_assign_to :project
26
+ # should_respond_with_content_type Mime::XML
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # See how the spec is readable: a ProjectsController responding to get show
32
+ # expects :find on Project which a mock project and then should assign to
33
+ # project and render template 'show'.
34
+ #
35
+ # Each macro before asserting will check if an action was already performed and
36
+ # if not, it runs the expectations and call the action.
37
+ #
38
+ # In other words, should assign to macro is basically doing:
39
+ #
40
+ # it 'should assign to project' do
41
+ # Project.should_receive(:find).with('37').and_return(mock_project)
42
+ # get :show, :id => '37'
43
+ # assigns(:project).should == mock_project
44
+ # end
45
+ #
46
+ # By default, all macros perform expectations. You can change
47
+ # this behavior sending :with_stubs or :with_expectations as options:
48
+ #
49
+ # should_assign_to :project, :with_stubs => true
50
+ # should_render_template 'show', :with_expectations => false
51
+ #
52
+ # This also works in the rspec way:
53
+ #
54
+ # it { should assign_to(:project).with_stubs }
55
+ # it { should render_template('show').with_expectations(false) }
56
+ #
57
+ # == Attention!
58
+ #
59
+ # If you need to check that an array is being sent to a method, you need to
60
+ # give an array inside another array, for example:
61
+ #
62
+ # expects :comment_ids=, :on => Post, :with => [1,2,3]
63
+ #
64
+ # Is the same as:
65
+ #
66
+ # Post.comment_ids = (1, 2, 3)
67
+ #
68
+ # And it won't work. The right way to handle this is:
69
+ #
70
+ # expects :comment_ids=, :on => Post, :with => [[1,2,3]]
71
+ #
72
+ # == mock_models
73
+ #
74
+ # You don't have to play with proc all the time. You can call mock_models which
75
+ # creates two class methods that simply returns a proc and a instance method that
76
+ # do the actual mock.
77
+ #
78
+ # describe ProjectsController do
79
+ # mock_models :project
80
+ #
81
+ # And it creates:
82
+ #
83
+ # def self.project_proc
84
+ # proc { mock_project }
85
+ # end
86
+ #
87
+ # # To be used on index actions
88
+ # def self.projects_proc
89
+ # proc { [mock_project] }
90
+ # end
91
+ #
92
+ # def mock_project(stubs={})
93
+ # @project ||= mock_model(Project, stubs)
94
+ # end
95
+ #
96
+ # Then you can replace those lines:
97
+ #
98
+ # expects :find, :on => Project, :with => '37', :returns => proc { mock_project }
99
+ # should_assign_to :project, :with => proc { mock_project }
100
+ #
101
+ # For:
102
+ #
103
+ # expects :find, :on => Project, :with => '37', :returns => project_proc
104
+ # should_assign_to :project, :with => project_proc
105
+ #
106
+ # = Give me more!
107
+ #
108
+ # If you need to set the example group description, you can also call <tt>get</tt>,
109
+ # <tt>post</tt>, <tt>put</tt> and <tt>delete</tt> methods:
110
+ #
111
+ # describe 'my description' do
112
+ # get :show, :id => 37
113
+ #
114
+ # Things start to get even better when we start to talk about nested resources.
115
+ # After our ProjectsController is created, we want to create a TasksController:
116
+ #
117
+ # describe TasksController do
118
+ # params :project_id => '42' #=> define params for all requests
119
+ #
120
+ # # Those two expectations get inherited in all describe groups below
121
+ # expects :find_by_title, :on => Project, :with => '42', :returns => project_proc
122
+ # expects :tasks, :and_return => Task
123
+ #
124
+ # describe :get => :show, :id => '37' do
125
+ # expects :find, :with => '37', :and_return => task_proc
126
+ #
127
+ # should_assign_to :project, :task
128
+ # should_render_template 'show'
129
+ # end
130
+ # end
131
+ #
132
+ # As you noticed, you can define parameters that will be available to all requests,
133
+ # using the method <tt>params</tt>.
134
+ #
135
+ # Finally if you need to write a spec by hand, you can invoke the action and
136
+ # expectations with run_action!, run_expectations! and run_stubs!. Examples:
137
+ #
138
+ # describe :get => :new do
139
+ # expects :new, :on => Project, :returns => project_proc
140
+ #
141
+ # it "should do something different" do
142
+ # run_action!
143
+ # # do you assertions here
144
+ # end
145
+ # end
146
+ #
147
+ # = Performance!
148
+ #
149
+ # Remarkable comes with a new way to speed up your tests. It performs the
150
+ # action inside a before(:all), so you can do:
151
+ #
152
+ # describe "responding to GET show" do
153
+ # get! :show, :id => 37
154
+ #
155
+ # should_assign_to :task
156
+ # should_render_template :show
157
+ # end
158
+ #
159
+ # Or in the compact way:
160
+ #
161
+ # describe :get! => :show, :id => 37
162
+ #
163
+ # The action will be performed just once before running the macros. If any
164
+ # error happens while performing the action, rspec will output an error
165
+ # but ALL the examples inside the example group (describe) won't be run.
166
+ #
167
+ # By now, the bang methods works only when integrate_views is true and this
168
+ # is when you must see a bigger performance gain.
169
+ #
170
+ # This feature comes with some rspec and rspec rails tweakings. So if you want
171
+ # to do something before the action is performed (stubs something or log
172
+ # someone in session), you have to do it giving a block to the action method:
173
+ #
174
+ # get! :show, :id => 37 do
175
+ # login_as(mock_user)
176
+ # end
177
+ #
178
+ # You can still use the compact way and give the block:
179
+ #
180
+ # describe :get => :show, :id => 37 do
181
+ # get! do
182
+ # login_as(mock_user)
183
+ # end
184
+ # end
185
+ #
186
+ class ExampleGroup
187
+ HTTP_VERBS_METHODS = [:get, :get!, :post, :post!, :put, :put!, :delete, :delete!]
188
+
189
+ class_inheritable_reader :expects_chain, :default_action, :default_mime,
190
+ :default_verb, :default_params, :default_xhr,
191
+ :before_all_block
192
+
193
+ module NewClassMethods
194
+
195
+ # Creates a chain that will be evaluated as stub or expectation. The
196
+ # first parameter is the method expected. You can also specify multiple
197
+ # methods to stub and give a block to calculate the returned value. See
198
+ # examples below.
199
+ #
200
+ # == Options
201
+ #
202
+ # * <tt>:on</tt> - Tell which object will receive the expected method.
203
+ # This option is always required.
204
+ #
205
+ # * <tt>:with</tt> - Tell each parameters will be sent with the expected
206
+ # method. This option is used only in expectations and is optional.
207
+ #
208
+ # * <tt>:returns</tt> - Tell what the expectations should return. Not
209
+ # required.
210
+ #
211
+ # * <tt>:times</tt> - The number of times the object will receive the
212
+ # method. Used only in expectations and when not given, defaults to 1.
213
+ #
214
+ # * <tt>:ordered</tt> - When true specifies that expectations should
215
+ # be received in order.
216
+ #
217
+ # == Example
218
+ #
219
+ # expects :new, :on => Project, :returns => :project_proc, :times => 2
220
+ #
221
+ # expects :new, :find, :on => Project, :returns => :project_proc
222
+ #
223
+ # expects :human_attribute_name, :on => Project, :with => :title do |attr|
224
+ # attr.to_s.humanize
225
+ # end
226
+ #
227
+ def expects(*args, &block)
228
+ options = args.extract_options!
229
+ options.assert_valid_keys(:on, :with, :returns, :times, :ordered)
230
+
231
+ args.each do |arg|
232
+ write_inheritable_array(:expects_chain, [ [ arg, options, block] ])
233
+ end
234
+ end
235
+
236
+ # The mime type of the request. The value given will be called transformed
237
+ # into a string and set in the @request.env['HTTP_ACCEPT'] variable.
238
+ #
239
+ # == Examples
240
+ #
241
+ # mime Mime::XML
242
+ # mime 'application/xml+rss'
243
+ #
244
+ def mime(mime)
245
+ write_inheritable_attribute(:default_mime, mime.to_s)
246
+ end
247
+
248
+ # The params used for the request. Calls are always nested:
249
+ #
250
+ # == Examples
251
+ #
252
+ # describe TasksController do
253
+ # params :project_id => 42
254
+ #
255
+ # describe :get => :show, :id => 37 do
256
+ # # will request with params {:id => 37, :project_id => 42}
257
+ # end
258
+ # end
259
+ #
260
+ def params(params)
261
+ write_inheritable_hash(:default_params, params)
262
+ end
263
+
264
+ # Sets the request to perform a XmlHttpRequest.
265
+ #
266
+ # == Examples
267
+ #
268
+ # describe TasksController do
269
+ # xhr!
270
+ # end
271
+ #
272
+ def xhr!(bool=true)
273
+ write_inheritable_attribute(:default_xhr, bool)
274
+ end
275
+
276
+ [:get, :post, :put, :delete].each do |verb|
277
+ module_eval <<-VERB, __FILE__, __LINE__
278
+ # Declares that we want to do a #{verb} request in the given action
279
+ # and with the given params.
280
+ #
281
+ # == Examples
282
+ #
283
+ # #{verb} :action, :id => 42
284
+ #
285
+ def #{verb}(action, params={})
286
+ params(params)
287
+ write_inheritable_attribute(:default_verb, #{verb.inspect})
288
+ write_inheritable_attribute(:default_action, action)
289
+ end
290
+ VERB
291
+ end
292
+
293
+ [:get!, :post!, :put!, :delete!].each do |verb|
294
+ module_eval <<-VERB, __FILE__, __LINE__
295
+ # Declares that we want to do a #{verb} request in the given action
296
+ # and with the given params, but the action is performed just once
297
+ # in the describe group. In other words, it's performed in a
298
+ # before(:all) filter.
299
+ #
300
+ # == Examples
301
+ #
302
+ # #{verb} :action, :id => 42
303
+ #
304
+ def #{verb}(action=nil, params={}, &block)
305
+ #{verb.to_s.chop}(action, params) if action
306
+ write_inheritable_array(:before_all_block, [block]) if block
307
+ run_callbacks_once!
308
+ end
309
+ VERB
310
+ end
311
+
312
+ # Undefine the method run_callbacks so rspec won't run them in the
313
+ # before and after :each cycle. Then we redefine it as run_callbacks_once,
314
+ # which will be used as an before(:all) and after(:all) filter.
315
+ #
316
+ def run_callbacks_once!(&block) #:nodoc:
317
+ unless instance_methods.any?{|m| m.to_s == 'run_callbacks_once' }
318
+ alias_method :run_callbacks_once, :run_callbacks
319
+ class_eval "def run_callbacks(*args); end"
320
+
321
+ before(:all) do
322
+ setup_mocks_for_rspec
323
+ run_callbacks_once :setup
324
+
325
+ before_all_block.each do |block|
326
+ instance_eval(&block)
327
+ end if before_all_block
328
+
329
+ run_action!
330
+ verify_mocks_for_rspec
331
+ teardown_mocks_for_rspec
332
+ end
333
+
334
+ after(:all) do
335
+ run_callbacks_once :teardown
336
+ end
337
+ end
338
+ end
339
+
340
+ # Overwrites describe to provide quick action description with I18n.
341
+ #
342
+ # You can now do:
343
+ #
344
+ # describe :get => :show, :id => 37
345
+ #
346
+ # Which is the same as:
347
+ #
348
+ # describe 'responding to #GET show' do
349
+ # get :show, :id => 37
350
+ #
351
+ # And do this:
352
+ #
353
+ # describe Mime::XML
354
+ #
355
+ # Which is the same as:
356
+ #
357
+ # describe 'with xml' do
358
+ # mime Mime::XML
359
+ #
360
+ # The string can be localized using I18n. An example yml file is:
361
+ #
362
+ # locale:
363
+ # remarkable:
364
+ # action_controller:
365
+ # responding: "responding to #{{verb}} {{action}}"
366
+ # mime_type: "with {{format}} ({{content_type}})"
367
+ #
368
+ # And load the locale file with:
369
+ #
370
+ # Remarkable.add_locale locale_path
371
+ #
372
+
373
+
374
+ def describe_with_verb_params(*args, &block)
375
+ options = args.first.is_a?(Hash) ? args.first : {}
376
+ verb = (options.keys & HTTP_VERBS_METHODS).first
377
+
378
+ if verb
379
+ action = options.delete(verb)
380
+ verb = verb.to_s
381
+
382
+ description = Remarkable.t 'remarkable.action_controller.responding',
383
+ :default => "responding to \#{{verb}} {{action}}",
384
+ :verb => verb.sub('!', '').upcase, :action => action
385
+
386
+ send_args = [ verb, action, options ]
387
+ elsif args.first.is_a?(Mime::Type)
388
+ mime = args.first
389
+
390
+ description = Remarkable.t 'remarkable.action_controller.mime_type',
391
+ :default => "with #{mime.to_sym}",
392
+ :format => mime.to_sym, :content_type => mime.to_s
393
+
394
+ send_args = [ :mime, mime ]
395
+ else # return if no special type was found
396
+ return describe_without_verb_params(*args, &block)
397
+ end
398
+
399
+ args.shift
400
+ args.unshift(description)
401
+
402
+ # Creates an example group, send the method and eval the given block.
403
+ #
404
+ example_group = describe_without_verb_params(*args) do
405
+ send(*send_args)
406
+ instance_eval(&block)
407
+ end
408
+ end
409
+
410
+
411
+ # Creates mock methods automatically.
412
+ #
413
+ # == Options
414
+ #
415
+ # * <tt>:as</tt> - Used to set the model . For example, if you have
416
+ # Admin::Task model, you have to tell the name of the class to be
417
+ # mocked:
418
+ #
419
+ # mock_models :admin_task, :as => "Admin::Task"
420
+ #
421
+ # * <tt>:class_method</tt> - When set to false, does not create the
422
+ # class method which returns a proc.
423
+ #
424
+ # == Examples
425
+ #
426
+ # Doing this:
427
+ #
428
+ # describe ProjectsController do
429
+ # mock_models :project
430
+ # end
431
+ #
432
+ # Will create one instance and two class mock methods for you:
433
+ #
434
+ # def self.project_proc
435
+ # proc { mock_project }
436
+ # end
437
+ #
438
+ # # To be used on index actions
439
+ # def self.projects_procs
440
+ # proc { [ mock_project ] }
441
+ # end
442
+ #
443
+ # def mock_project(stubs={})
444
+ # @project ||= mock_model(Project, stubs)
445
+ # end
446
+ #
447
+ # If you want to create just the instance method, you can give
448
+ # :class_method => false as option.
449
+ #
450
+ def mock_models(*models)
451
+ options = models.extract_options!
452
+ options = { :class_method => true }.merge(options)
453
+
454
+ models.each do |model|
455
+ model = model.to_s
456
+ klass = options[:as] || model.classify
457
+
458
+ if options[:class_method]
459
+ (class << self; self; end).class_eval <<-METHOD
460
+ def #{model}_proc; proc { mock_#{model} }; end
461
+ def #{model.pluralize}_proc; proc { [ mock_#{model} ] }; end
462
+
463
+ alias :mock_#{model} :#{model}_proc
464
+ alias :mock_#{model.pluralize} :#{model.pluralize}_proc
465
+ METHOD
466
+ end
467
+
468
+ self.class_eval <<-METHOD
469
+ def mock_#{model}(stubs={})
470
+ @#{model} ||= mock_model(#{klass}, stubs)
471
+ end
472
+ METHOD
473
+ end
474
+ end
475
+ alias :mock_model :mock_models
476
+
477
+ end
478
+
479
+ protected
480
+
481
+ # Evaluates the expectation chain as stub or expectations.
482
+ #
483
+ def evaluate_expectation_chain(use_expectations=true) #:nodoc:
484
+ return if self.expects_chain.nil?
485
+
486
+ self.expects_chain.each do |method, options, block|
487
+ object = evaluate_value(options[:on])
488
+ raise ScriptError, "You have to give me :on as an option when calling :expects." if object.nil?
489
+
490
+ if use_expectations
491
+ chain = object.should_receive(method)
492
+
493
+ if options.key?(:with)
494
+ with = evaluate_value(options[:with])
495
+
496
+ chain = if with.is_a?(Array)
497
+ chain.with(*with)
498
+ else
499
+ chain.with(with)
500
+ end
501
+ end
502
+
503
+ times = options[:times] || 1
504
+ chain = chain.exactly(times).times
505
+
506
+ chain = chain.ordered if options[:ordered]
507
+ else
508
+ chain = object.stub!(method)
509
+ end
510
+
511
+ chain = if block
512
+ chain.and_return(&block)
513
+ else
514
+ return_value = evaluate_value(options[:returns])
515
+ chain.and_return(return_value)
516
+ end
517
+ end
518
+ end
519
+
520
+ # Instance method run_stubs! if someone wants to declare additional
521
+ # tests and call the stubs inside of it.
522
+ #
523
+ def run_stubs!
524
+ evaluate_expectation_chain(false)
525
+ end
526
+
527
+ # Instance method run_expectations! if someone wants to declare
528
+ # additional tests and call the stubs inside of it.
529
+ #
530
+ def run_expectations!
531
+ evaluate_expectation_chain(true)
532
+ end
533
+
534
+ # Run the action declared in the describe group, but before runs also
535
+ # the expectations. If an action was already performed, it doesn't run
536
+ # anything at all and returns false.
537
+ #
538
+ # The first parameter is if you want to run expectations or stubs. You
539
+ # can also supply the verb (get, post, put or delete), which action to
540
+ # call, parameters, the mime type and if a xhr should be performed. If
541
+ # any of those parameters are supplied, they override the current
542
+ # definition.
543
+ #
544
+ def run_action!(use_expectations=true, verb=nil, action=nil, params=nil, mime=nil, xhr=nil)
545
+ return false if controller.send(:performed?)
546
+
547
+ evaluate_expectation_chain(use_expectations)
548
+
549
+ mime ||= default_mime
550
+ verb ||= default_verb
551
+ action ||= default_action
552
+ params ||= default_params
553
+ xhr ||= default_xhr
554
+
555
+ parent = self.class.parent
556
+ until mime || verb || action || params || xhr
557
+ mime ||= parent.default_mime
558
+ verb ||= parent.default_verb
559
+ action ||= parent.default_action
560
+ params ||= parent.default_params
561
+ xhr ||= parent.default_xhr
562
+ parent = parent.parent
563
+ end
564
+
565
+ raise ScriptError, "No action was performed or declared." unless verb && action
566
+
567
+ request.env["HTTP_ACCEPT"] ||= mime.to_s if mime
568
+ request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' if xhr
569
+ send(verb, action, params)
570
+ end
571
+
572
+ # Evaluate a given value.
573
+ #
574
+ # This allows procs to be given to the expectation chain and they will
575
+ # be evaluated in the instance binding.
576
+ #
577
+ def evaluate_value(duck) #:nodoc:
578
+ if duck.is_a?(Proc)
579
+ self.instance_eval(&duck)
580
+ elsif duck.is_a?(Array)
581
+ duck.map{|child_duck| evaluate_value(child_duck) }
582
+ else
583
+ duck
584
+ end
585
+ end
586
+
587
+ extend NewClassMethods
588
+ class << self
589
+ alias_method :describe_without_verb_params, :describe
590
+ alias_method :describe, :describe_with_verb_params
591
+ end
592
+ end
593
+ end
594
+ end