PageRenderTime 30ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/context_on_crack/controller_example_group_methods.rb

https://github.com/technoweenie/context_on_crack
Ruby | 461 lines | 272 code | 45 blank | 144 comment | 22 complexity | 0f9913d96466195d19b1a3bcc5f3ad9d MD5 | raw file
  1. module ContextOnCrack
  2. module ControllerExampleGroupMethods
  3. def self.extended(base)
  4. base.send :include, InstanceMethods
  5. end
  6. def describe_access(&block)
  7. ContextOnCrack::ControllerAccessProxy.new(self, controller_class).instance_eval(&block)
  8. end
  9. def describe_requests(&block)
  10. ContextOnCrack::ControllerRequestProxy.new(self, controller_class).instance_eval(&block)
  11. end
  12. module InstanceMethods
  13. def acting(&block)
  14. act!
  15. block.call(@response) if block
  16. @response
  17. end
  18. def act!
  19. instance_eval &@acting_block
  20. end
  21. protected
  22. def asserts_content_type(type = :html)
  23. mime = Mime::Type.lookup_by_extension((type || :html).to_s)
  24. assert_equal mime, @response.content_type, "Renders with Content-Type of #{@response.content_type}, not #{mime}"
  25. end
  26. def asserts_status(status)
  27. case status
  28. when String, Fixnum
  29. assert_equal status.to_s, @response.code, "Renders with status of #{@response.code.inspect}, not #{status}"
  30. when Symbol
  31. code_value = ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[status]
  32. assert_equal code_value.to_s, @response.code, "Renders with status of #{@response.code.inspect}, not #{code_value.inspect} (#{status.inspect})"
  33. else
  34. assert_equal "200", @response.code, "Is not successful"
  35. end
  36. end
  37. end
  38. end
  39. class ControllerProxy
  40. attr_reader :test_case
  41. def initialize(test_case, controller, context_name)
  42. @test_case, @controller = test_case.context(context_name) {}, controller
  43. end
  44. protected
  45. def method_missing(m, *args, &block)
  46. @test_case.send m, *args, &block
  47. end
  48. end
  49. class ControllerGroup
  50. def initialize(proxy)
  51. @proxy = proxy
  52. end
  53. protected
  54. def method_missing(m, *args, &block)
  55. @proxy.test_case.send(m, *args, &block)
  56. end
  57. end
  58. class ControllerAccessProxy < ControllerProxy
  59. def initialize(test_case, controller)
  60. super test_case, controller, "access"
  61. end
  62. def as(*users, &block)
  63. users.each do |user|
  64. ControllerAccessGroup.new(self, user).instance_eval(&block)
  65. end
  66. end
  67. end
  68. class ControllerAccessGroup < ControllerGroup
  69. def initialize(proxy, user)
  70. super(proxy)
  71. @user = user
  72. end
  73. def it_performs(description, method, actions, params = {}, &block)
  74. Array(actions).each do |action|
  75. param_desc = (params.respond_to?(:call) && params.respond_to?(:to_ruby)) ?
  76. params.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip :
  77. params.inspect
  78. action_user = @user
  79. it "#{description} for @#{@user}: #{method.to_s.upcase} #{action} #{param_desc}".strip do
  80. stub(@controller).current_user { action_user == :anon ? nil : instance_variable_get("@#{action_user}") }
  81. stub(@controller).logged_in? { action_user != :anon }
  82. meta = class << @controller ; self ; end
  83. meta.send(:define_method, action) { head :ok }
  84. send method, action, params.respond_to?(:call) ? instance_eval(&params) : params
  85. instance_eval &block
  86. end
  87. end
  88. end
  89. def it_allows(method, actions, params = {}, &block_params)
  90. it_performs :allows, method, actions, block_params || params do
  91. assert_equal "200", @response.code, "Is not successful"
  92. end
  93. end
  94. def it_restricts(method, actions, params = {}, &block_params)
  95. it_performs :restricts, method, actions, block_params || params do
  96. assert_redirected_to new_session_path
  97. end
  98. end
  99. end
  100. class ControllerRequestProxy < ControllerProxy
  101. def initialize(test_case, controller)
  102. super test_case, controller, "request"
  103. end
  104. def context(description, &block)
  105. ControllerRequestGroup.new(self, "#{@controller.name} #{description}", @before_blocks, @after_blocks).instance_eval(&block)
  106. end
  107. end
  108. class ControllerRequestGroup < ControllerGroup
  109. @@variable_types = {:headers => :to_s, :flash => nil, :session => nil}
  110. def initialize(proxy, prefix, before_blocks, after_blocks)
  111. super(proxy)
  112. @prefix = prefix
  113. @acting_block = nil
  114. @before_blocks = before_blocks.nil? ? [] : before_blocks.dup
  115. @after_blocks = after_blocks.nil? ? [] : after_blocks.dup
  116. end
  117. def acting_block
  118. @acting_block
  119. end
  120. def act!(&block)
  121. @acting_block = block
  122. end
  123. def before(period = :each, &block)
  124. raise "only before(:each) allowed in controller request proxy tests" if period != :each
  125. @before_blocks << block
  126. end
  127. def after(period = :each, &block)
  128. raise "only after(:each) allowed in controller request proxy tests" if period != :each
  129. @after_blocks << block
  130. end
  131. def context(description, &block)
  132. ControllerRequestGroup.new(@proxy, "#{@prefix} #{description}", @before_blocks, @after_blocks).instance_eval(&block)
  133. end
  134. def it(description, &block)
  135. before_blocks = @before_blocks
  136. group_acting_block = @acting_block
  137. after_blocks = @after_blocks
  138. @proxy.test_case.it("#{@prefix} #{description}") do
  139. @acting_block = group_acting_block
  140. before_blocks.each { |b| instance_eval &b }
  141. instance_eval &block
  142. after_blocks.each { |b| instance_eval &b }
  143. end
  144. end
  145. # Checks that the action redirected:
  146. #
  147. # it_redirects_to { foo_path(@foo) }
  148. #
  149. # Provide a better hint than Proc#inspect
  150. #
  151. # it_redirects_to("foo_path(@foo)") { foo_path(@foo) }
  152. #
  153. def it_redirects_to(hint = nil, &route)
  154. if hint.nil? && route.respond_to?(:to_ruby)
  155. hint = route.to_ruby.gsub(/(^proc \{)|(\}$)/, '').strip
  156. end
  157. it "redirects to #{(hint || route).inspect}" do
  158. act!
  159. assert_redirected_to instance_eval(&route)
  160. end
  161. end
  162. # Check that an instance variable was set to the instance variable of the same name
  163. # in the Spec Example:
  164. #
  165. # it_assigns :foo # => assigns[:foo].should == @foo
  166. #
  167. # If there is no instance variable @foo, it will just check to see if its not nil:
  168. #
  169. # it_assigns :foo # => assigns[:foo].should_not be_nil (if @foo is not defined in spec)
  170. #
  171. # Check multiple instance variables
  172. #
  173. # it_assigns :foo, :bar
  174. #
  175. # Check the instance variable was set to something more specific
  176. #
  177. # it_assigns :foo => 'bar'
  178. #
  179. # Check both instance variables:
  180. #
  181. # it_assigns :foo, :bar => 'bar'
  182. #
  183. # Check the instance variable is not nil:
  184. #
  185. # it_assigns :foo => :not_nil # assigns[:foo].should_not be_nil
  186. #
  187. # Check the instance variable is nil
  188. #
  189. # it_assigns :foo => nil # => assigns[:foo].should be_nil
  190. #
  191. # Check the instance variable was not set at all
  192. #
  193. # it_assigns :foo => :undefined # => controller.send(:instance_variables).should_not include("@foo")
  194. #
  195. # Instance variables for :headers/:flash/:session are special and use the assigns_* methods.
  196. #
  197. # it_assigns :foo => 'bar',
  198. # :headers => { :Location => '...' }, # it.assigns_headers :Location => ...
  199. # :flash => { :notice => :not_nil }, # it.assigns_flash :notice => ...
  200. # :session => { :user => 1 }, # it.assigns_session :user => ...
  201. #
  202. def it_assigns(*names)
  203. names.each do |name|
  204. if name.is_a?(Symbol)
  205. it_assigns name => name # go forth and recurse!
  206. elsif name.is_a?(Hash)
  207. name.each do |key, value|
  208. if @@variable_types.key?(key) then send("it_assigns_#{key}", value)
  209. else it_assigns_example_values(key, value) end
  210. end
  211. end
  212. end
  213. end
  214. # See protected #render_blank, #render_template, and #render_xml for details.
  215. #
  216. # it_renders :blank
  217. # it_renders :template, :new
  218. # it_renders :xml, :foo
  219. #
  220. def it_renders(render_method, *args, &block)
  221. send("it_renders_#{render_method}", *args, &block)
  222. end
  223. # Check that the flash variable(s) were assigned
  224. #
  225. # it_assigns_flash :notice => 'foo',
  226. # :this_is_nil => nil,
  227. # :this_is_undefined => :undefined,
  228. # :this_is_set => :not_nil
  229. #
  230. def it_assigns_flash(flash)
  231. raise NotImplementedError
  232. end
  233. # Check that the session variable(s) were assigned
  234. #
  235. # it_assigns_session :notice => 'foo',
  236. # :this_is_nil => nil,
  237. # :this_is_undefined => :undefined,
  238. # :this_is_set => :not_nil
  239. #
  240. def it_assigns_session(session)
  241. raise NotImplementedError
  242. end
  243. # Check that the HTTP header(s) were assigned
  244. #
  245. # it.assigns_headers :Location => 'foo',
  246. # :this_is_nil => nil,
  247. # :this_is_undefined => :undefined,
  248. # :this_is_set => :not_nil
  249. #
  250. def it_assigns_headers(headers)
  251. raise NotImplementedError
  252. end
  253. @@variable_types.each do |collection_type, collection_op|
  254. public
  255. define_method "it_assigns_#{collection_type}" do |values|
  256. values.each do |key, value|
  257. send("it_assigns_#{collection_type}_values", key, value)
  258. end
  259. end
  260. protected
  261. define_method "it_assigns_#{collection_type}_values" do |key, value|
  262. key = key.send(collection_op) if collection_op
  263. it "assigns #{collection_type}[#{key.inspect}]" do
  264. acting do |resp|
  265. collection = resp.send(collection_type)
  266. case value
  267. when nil
  268. assert_nil collection[key]
  269. when :not_nil
  270. assert_not_nil collection[key]
  271. when :undefined
  272. assert !collection.include?(key), "#{collection_type} includes #{key}"
  273. when Proc
  274. assert_equal instance_eval(&value), collection[key]
  275. else
  276. assert_equal value, collection[key]
  277. end
  278. end
  279. end
  280. end
  281. end
  282. public
  283. def it_assigns_example_values(name, value)
  284. it "assigns @#{name}" do
  285. act!
  286. value =
  287. case value
  288. when :not_nil
  289. assert_not_nil assigns(name), "@#{name} is nil"
  290. when :undefined
  291. assert !@controller.send(:instance_variables).include?("@#{name}"), "@#{name} is defined"
  292. when Symbol
  293. if (instance_variable = instance_variable_get("@#{value}")).nil?
  294. assert_not_nil assigns(name)
  295. else
  296. assert_equal instance_variable, assigns(name)
  297. end
  298. end
  299. end
  300. end
  301. # Creates 2 examples: One to check that the body is blank,
  302. # and the other to check the status. It looks for one option:
  303. # :status. If unset, it checks that that the response was a success.
  304. # Otherwise it takes an integer or a symbol and matches the status code.
  305. #
  306. # it_renders :blank
  307. # it_renders :blank, :status => :not_found
  308. #
  309. def it_renders_blank(options = {})
  310. it "renders a blank response" do
  311. acting do |response|
  312. asserts_status options[:status]
  313. assert @response.body.strip.blank?
  314. end
  315. end
  316. end
  317. # Creates 3 examples: One to check that the given template was rendered.
  318. # It looks for two options: :status and :format.
  319. #
  320. # it_renders :template, :index
  321. # it_renders :template, :index, :status => :not_found
  322. # it_renders :template, :index, :format => :xml
  323. #
  324. # If :status is unset, it checks that that the response was a success.
  325. # Otherwise it takes an integer or a symbol and matches the status code.
  326. #
  327. # If :format is unset, it checks that the action is Mime:HTML. Otherwise
  328. # it attempts to match the mime type using Mime::Type.lookup_by_extension.
  329. #
  330. def it_renders_template(template_name, options = {})
  331. it "renders #{template_name}" do
  332. acting do |response|
  333. asserts_status options[:status]
  334. asserts_content_type options[:format]
  335. assert_template template_name.to_s
  336. end
  337. end
  338. end
  339. # Creates 3 examples: One to check that the given XML was returned.
  340. # It looks for two options: :status and :format.
  341. #
  342. # Checks that the xml matches a given string
  343. #
  344. # it_renders(:xml) { "<foo />" }
  345. #
  346. # Checks that the xml matches @foo.to_xml
  347. #
  348. # it_renders :xml, :foo
  349. #
  350. # Checks that the xml matches @foo.errors.to_xml
  351. #
  352. # it_renders :xml, "foo.errors"
  353. #
  354. # it_renders :xml, :index, :status => :not_found
  355. # it_renders :xml, :index, :format => :xml
  356. #
  357. # If :status is unset, it checks that that the response was a success.
  358. # Otherwise it takes an integer or a symbol and matches the status code.
  359. #
  360. # If :format is unset, it checks that the action is Mime:HTML. Otherwise
  361. # it attempts to match the mime type using Mime::Type.lookup_by_extension.
  362. #
  363. def it_renders_xml(record = nil, options = {}, &block)
  364. it_renders_xml_or_json :xml, record, options, &block
  365. end
  366. # Creates 3 examples: One to check that the given JSON was returned.
  367. # It looks for two options: :status and :format.
  368. #
  369. # Checks that the json matches a given string
  370. #
  371. # it_renders(:json) { "{}" }
  372. #
  373. # Checks that the json matches @foo.to_json
  374. #
  375. # it_renders :json, :foo
  376. #
  377. # Checks that the json matches @foo.errors.to_json
  378. #
  379. # it_renders :json, "foo.errors"
  380. #
  381. # it_renders :json, :index, :status => :not_found
  382. # it_renders :json, :index, :format => :json
  383. #
  384. # If :status is unset, it checks that that the response was a success.
  385. # Otherwise it takes an integer or a symbol and matches the status code.
  386. #
  387. # If :format is unset, it checks that the action is Mime:HTML. Otherwise
  388. # it attempts to match the mime type using Mime::Type.lookup_by_extension.
  389. #
  390. def it_renders_json(record = nil, options = {}, &block)
  391. it_renders_xml_or_json :json, record, options, &block
  392. end
  393. def it_renders_xml_or_json(format, record = nil, options = {}, &block)
  394. if record.is_a?(Hash)
  395. options = record
  396. record = nil
  397. end
  398. it "renders #{format}" do
  399. if record
  400. pieces = record.to_s.split(".")
  401. record = instance_variable_get("@#{pieces.shift}")
  402. record = record.send(pieces.shift) until pieces.empty?
  403. block ||= lambda { record.send("to_#{format}") }
  404. end
  405. acting do |response|
  406. asserts_status options[:status]
  407. asserts_content_type options[:format] || format
  408. if block
  409. assert false, "no response.should have_text"
  410. response.should have_text(block.call)
  411. end
  412. end
  413. end
  414. end
  415. end
  416. end