Blob of rspec_on_rails/lib/spec/rails/example/controller_example_group.rb (raw blob data)

1 module Spec
2 module Rails
3 module Example
4 # Controller Examples live in $RAILS_ROOT/spec/controllers/.
5 #
6 # Controller Examples use Spec::Rails::Example::ControllerExampleGroup, which supports running specs for
7 # Controllers in two modes, which represent the tension between the more granular
8 # testing common in TDD and the more high level testing built into
9 # rails. BDD sits somewhere in between: we want to a balance between
10 # specs that are close enough to the code to enable quick fault
11 # isolation and far enough away from the code to enable refactoring
12 # with minimal changes to the existing specs.
13 #
14 # == Isolation mode (default)
15 #
16 # No dependencies on views because none are ever rendered. The
17 # benefit of this mode is that can spec the controller completely
18 # independent of the view, allowing that responsibility to be
19 # handled later, or by somebody else. Combined w/ separate view
20 # specs, this also provides better fault isolation.
21 #
22 # == Integration mode
23 #
24 # To run in this mode, include the +integrate_views+ declaration
25 # in your controller context:
26 #
27 # describe ThingController do
28 # integrate_views
29 # ...
30 #
31 # In this mode, controller specs are run in the same way that
32 # rails functional tests run - one set of tests for both the
33 # controllers and the views. The benefit of this approach is that
34 # you get wider coverage from each spec. Experienced rails
35 # developers may find this an easier approach to begin with, however
36 # we encourage you to explore using the isolation mode and revel
37 # in its benefits.
38 #
39 # == Expecting Errors
40 #
41 # Rspec on Rails will raise errors that occur in controller actions.
42 # In contrast, Rails will swallow errors that are raised in controller
43 # actions and return an error code in the header. If you wish to override
44 # Rspec and have Rail's default behaviour,tell the controller to use
45 # rails error handling ...
46 #
47 # before(:each) do
48 # controller.use_rails_error_handling!
49 # end
50 #
51 # When using Rail's error handling, you can expect error codes in headers ...
52 #
53 # it "should return an error in the header" do
54 # response.should be_error
55 # end
56 #
57 # it "should return a 501" do
58 # response.response_code.should == 501
59 # end
60 #
61 # it "should return a 501" do
62 # response.code.should == "501"
63 # end
64 class ControllerExampleGroup < FunctionalExampleGroup
65 class << self
66
67 # Use this to instruct RSpec to render views in your controller examples (Integration Mode).
68 #
69 # describe ThingController do
70 # integrate_views
71 # ...
72 #
73 # See Spec::Rails::Example::ControllerExampleGroup for more information about
74 # Integration and Isolation modes.
75 def integrate_views(integrate_views = true)
76 @integrate_views = integrate_views
77 end
78
79 def integrate_views? # :nodoc:
80 @integrate_views
81 end
82
83 def inherited(klass) # :nodoc:
84 klass.controller_class_name = controller_class_name
85 klass.integrate_views(integrate_views?)
86 super
87 end
88
89 # You MUST provide a controller_name within the context of
90 # your controller specs:
91 #
92 # describe "ThingController" do
93 # controller_name :thing
94 # ...
95 def controller_name(name)
96 @controller_class_name = "#{name}_controller".camelize
97 end
98 attr_accessor :controller_class_name # :nodoc:
99 end
100
101 before(:each) do
102 # Some Rails apps explicitly disable ActionMailer in environment.rb
103 if defined?(ActionMailer)
104 @deliveries = []
105 ActionMailer::Base.deliveries = @deliveries
106 end
107
108 unless @controller.class.ancestors.include?(ActionController::Base)
109 Spec::Expectations.fail_with <<-EOE
110 You have to declare the controller name in controller specs. For example:
111 describe "The ExampleController" do
112 controller_name "example" #invokes the ExampleController
113 end
114 EOE
115 end
116 (class << @controller; self; end).class_eval do
117 def controller_path #:nodoc:
118 self.class.name.underscore.gsub('_controller', '')
119 end
120 include ControllerInstanceMethods
121 end
122 @controller.integrate_views! if @integrate_views
123 @controller.session = session
124 end
125
126 attr_reader :response, :request, :controller
127
128 def initialize(defined_description, &implementation) #:nodoc:
129 super
130 controller_class_name = self.class.controller_class_name
131 if controller_class_name
132 @controller_class_name = controller_class_name.to_s
133 else
134 @controller_class_name = self.class.described_type.to_s
135 end
136 @integrate_views = self.class.integrate_views?
137 end
138
139 # Uses ActionController::Routing::Routes to generate
140 # the correct route for a given set of options.
141 # == Example
142 # route_for(:controller => 'registrations', :action => 'edit', :id => 1)
143 # => '/registrations/1;edit'
144 def route_for(options)
145 ensure_that_routes_are_loaded
146 ActionController::Routing::Routes.generate(options)
147 end
148
149 # Uses ActionController::Routing::Routes to parse
150 # an incoming path so the parameters it generates can be checked
151 # == Example
152 # params_from(:get, '/registrations/1;edit')
153 # => :controller => 'registrations', :action => 'edit', :id => 1
154 def params_from(method, path)
155 ensure_that_routes_are_loaded
156 ActionController::Routing::Routes.recognize_path(path, :method => method)
157 end
158
159 protected
160 def _assigns_hash_proxy
161 @_assigns_hash_proxy ||= AssignsHashProxy.new @controller
162 end
163
164 private
165 def ensure_that_routes_are_loaded
166 ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
167 end
168
169 module ControllerInstanceMethods #:nodoc:
170 include Spec::Rails::Example::RenderObserver
171
172 # === render(options = nil, deprecated_status_or_extra_options = nil, &block)
173 #
174 # This gets added to the controller's singleton meta class,
175 # allowing Controller Examples to run in two modes, freely switching
176 # from context to context.
177 def render(options=nil, deprecated_status_or_extra_options=nil, &block)
178 if ::Rails::VERSION::STRING >= '2.0.0' && deprecated_status_or_extra_options.nil?
179 deprecated_status_or_extra_options = {}
180 end
181
182 unless block_given?
183 unless integrate_views?
184 if @template.respond_to?(:finder)
185 (class << @template.finder; self; end).class_eval do
186 define_method :file_exists? do
187 true
188 end
189 end
190 else
191 (class << @template; self; end).class_eval do
192 define_method :file_exists? do
193 true
194 end
195 end
196 end
197 (class << @template; self; end).class_eval do
198 define_method :render_file do |*args|
199 @first_render ||= args[0]
200 end
201 end
202 end
203 end
204
205 if matching_message_expectation_exists(options)
206 expect_render_mock_proxy.render(options, &block)
207 @performed_render = true
208 else
209 unless matching_stub_exists(options)
210 super(options, deprecated_status_or_extra_options, &block)
211 end
212 end
213 end
214
215 private
216 def matching_message_expectation_exists(options)
217 expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
218 end
219
220 def matching_stub_exists(options)
221 expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
222 end
223
224 public
225 if self.respond_to?(:should_receive) && self.respond_to?(:stub!)
226 self.send :alias_method, :orig_should_receive, :should_receive
227 self.send :alias_method, :orig_stub!, :stub!
228 def raise_with_disable_message(old_method, new_method)
229 raise %Q|
230 controller.#{old_method}(:render) has been disabled because it
231 can often produce unexpected results. Instead, you should
232 use the following (before the action):
233
234 controller.#{new_method}(*args)
235
236 See the rdoc for #{new_method} for more information.
237 |
238 end
239 def should_receive(*args)
240 if args[0] == :render
241 raise_with_disable_message("should_receive", "expect_render")
242 else
243 orig_should_receive(*args)
244 end
245 end
246 def stub!(*args)
247 if args[0] == :render
248 raise_with_disable_message("stub!", "stub_render")
249 else
250 orig_stub!(*args)
251 end
252 end
253 end
254
255 def response(&block)
256 # NOTE - we're setting @update for the assert_select_spec - kinda weird, huh?
257 @update = block
258 @_response || @response
259 end
260
261 def integrate_views!
262 @integrate_views = true
263 end
264
265 private
266
267 def integrate_views?
268 @integrate_views
269 end
270 end
271
272 Spec::Example::ExampleGroupFactory.register(:controller, self)
273 end
274 end
275 end
276 end