Permalink
Browse files

Merge 'aiwilliams/master'

* aiwilliams/master: (24 commits)
  Updated to work with rspec/rails 1.2.7
  Need a better solution. This file was causing application to not load in production/staging
  More informative when conditions are not met for caching tests to run
  cache_action matcher takes second argument for defining store cache option expectations.
  Action cache testing. See with_caching and cache_action.
  Fixed bug where non-string values would cause submit_form to fail
  Fixed bug where special characters in form submissions would mangle the value
  Added a test to prove how submit_form can forward headers
  click_on now supports :headers option
  Fixed bug where file fields were not working with Rack
  Fixed bug where arrays were not handled correctly in form submission
  Working with rails 2.3.1
  needed this to get form submission with selects working
  Fixed bug where navigate_to was submitting options to test_process and that signature has changed
  A bit of an update to the readme
  Running in Rails 2.3
  Don't alias rescue_action_without_fast_errors...instead call it directly when in an integration example
  Navigation was not always unescaping hrefs
  Navigation was not always unescaping hrefs
  Fix for Rails 2.2.1 - should call is not making it to the integration session without a direct reference. [Steve Iannopollo]
  ...

Conflicts:
	Rakefile
  • Loading branch information...
2 parents 988af28 + 5b992ac commit 66bb06d8b98ef59d2e8bb11e8411ccb3827fa338 @cradle cradle committed Sep 11, 2009
View
3 .gitignore
@@ -0,0 +1,3 @@
+vendor
+environments
+tmp
View
8 LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2007, Adam Williams and John W. Long.
+Copyright (c) 2007-2009, Adam Williams and John W. Long.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -16,4 +16,8 @@ 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.
+SOFTWARE.
+
+
+Cache testing portions taken from http://github.com/avdgaag/rspec-caching-test-plugin
+Copyright (c) 2008 Arjan van der Gaag, released under the MIT license
View
61 README
@@ -1,39 +1,28 @@
== Rails Spec Integration Plugin
-The RSpec Development team has been working hard to bring Stories to RSpec.
-That is great stuff, and certainly worth looking into. So what does that have
-to do with this plugin?
+The Spec Integration plugin brings together RSpec on Rails and Rails integration
+testing, while also adding a number of very useful new matchers and some more
+integration testing DSL methods.
-As of RSpec 1.0.8, it was suggested that if you want to write integration
-tests for Rails, you should continue to write Rails IntegrationTests,
-presumably test/unit style. Now with Stories, rspec_on_rails has begun to
-implement integration testing as a special kind of StoryRunner. This means you
-have to write RSpec Story style tests, implement StepMatchers, and all what
-else Stories entail. Hmmmm.
+=== Installation
-With the Spec Integration plugin, you get the wonders of RSpec without the
-complexity and expense of Stories. As a bonus, you also get some additional
-great tools for writing robust Rails integration tests.
-
-The Spec Integration plugin changes controller exception handling so it should
-not be loaded when your controller specs depend on that. I.e. it is not possible
-to cleanly run integration specs and controller specs as part of one test suite.
+script/plugin install git://github.com/aiwilliams/spec_integration.git
=== Quick Start
It's easy to get started. Create a directory named 'integration' in your spec
directory, right alongside your controllers, models, and views spec
-directories. Require your spec_helper at the top of new files in that
-directory. The rest is, as they say, a simple matter of programming ;)
+directories. In your spec_helper:
+
+ require 'spec/integration'
A simple integration spec looks like this:
# in spec/integration/signup_spec.rb
require File.dirname(__FILE__) + "/../spec_helper"
- require 'spec/integration'
describe "Logging in" do
- scenario :single_person
+ dataset :single_person
it "should send a user to the home page after login" do
# get the login page
@@ -59,10 +48,9 @@ example explains some of these features best.
# in spec/integration/signup_spec.rb
require File.dirname(__FILE__) + "/../spec_helper"
- require 'spec/integration'
describe "Logging in" do
- scenario :single_person
+ dataset :single_person
before do
navigate_to login_path
@@ -76,7 +64,7 @@ example explains some of these features best.
end
end
-The scenario method is provided by our other plugin, Scenarios(1). It's a
+The dataset method is provided by another plugin, Dataset(1). It's a
great alternative to fixtures. navigate_to, submit_form, and be_showing are a
few of the DSL methods provided by the Spec Integration plugin.
@@ -93,25 +81,18 @@ be_showing returns a matcher that ensures the response has the expected path.
=== More Information
-For more information, be sure to look through the documentation over at
-RubyForge:
+(1) http://github.com/aiwilliams/dataset
-* http://faithfulcode.rubyforge.org/docs/spec_integration
-
-You might also enjoy taking a look at the specs for the plugin:
-
-* http://faithfulcode.rubyforge.org/svn/plugins/trunk/spec_integration/spec
-
-Browse the complete source code:
-
-* http://faithfulcode.rubyforge.org/svn/plugins/trunk/spec_integration
+== Credits
+Written by [Adam Williams](http://github.com/aiwilliams).
+
+Contributors:
-(1) http://faithfulcode.rubyforge.org/docs/scenarios
+- [John Long](http://github.com/jlong)
+- [Steve Iannopollo](http://github.com/siannopollo)
+- [Austin Taylor](http://github.com/dotjerky)
-=== License
+---
-The Spec Integration plugin is released under the MIT-License and is Copyright
-(c) 2007, Adam Williams and John W. Long. Special thanks to Austin Taylor
-(dotjerky) and Steve Iannopollo for their part in helping us get this plugin
-ready for the public.
+Spec Integration is released under the MIT-License and is Copyright (c)2007-2009 Adam Williams.
View
10 Rakefile
@@ -1,9 +1,9 @@
+$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
+
require 'rubygems'
-gem 'rake'
-require 'rake'
-require 'rake/rdoctask'
+require 'spec/rake/spectask'
-require "#{File.dirname(__FILE__)}/spec/environment"
+task :default => :spec
desc "Run all specs"
task :spec => ["spec:libs:checkout"] do
@@ -49,4 +49,4 @@ Rake::RDocTask.new(:doc) do |r|
r.rdoc_dir = "doc"
end
-task :default => :spec
+task :default => :spec
View
3 init.rb
@@ -1,3 +0,0 @@
-# Placeholder to satisfy rails
-# Auto-Loading of `spec/integration` messes with RSpec's controller specs aswell
-# so make sure to explicitly `require spec/integration` *only* in integration specs
View
18 lib/spec/integration.rb
@@ -1,10 +1,22 @@
-require 'spec'
require 'spec/rails'
-require 'spec/integration/extensions'
+require 'spec/integration/extensions/hash'
require 'spec/integration/matchers'
require 'spec/integration/dsl'
+require 'spec/integration/extensions/action_controller/base'
+require 'spec/integration/extensions/action_controller/caching'
+require 'spec/integration/extensions/spec/rails/example/integration_example_group'
module Spec # :nodoc:
module Integration # :nodoc:
+
+ def ensure_caching_enabled
+ unless ActionController::Base.perform_caching
+ raise "ActiveRecord::Base.caches_action is not registering a caching filter when classes are loaded. Please modify your test environment file to have 'config.action_controller.perform_caching = true'."
+ end
+ end
+ module_function :ensure_caching_enabled
+
end
-end
+end
+
+ActionController::Base.cache_store = Spec::Integration::Extensions::ActionController::Caching::TestStore.new
View
7 lib/spec/integration/dsl.rb
@@ -1,7 +1,6 @@
-Dir[File.dirname(__FILE__) + '/dsl/extensions/*.rb'].each { |f| require f }
-require File.dirname(__FILE__) + '/dsl/navigation'
-require File.dirname(__FILE__) + '/dsl/form'
-require File.dirname(__FILE__) + '/dsl/integration_example_group'
+require 'spec/integration/dsl/caching'
+require 'spec/integration/dsl/navigation'
+require 'spec/integration/dsl/form'
module Spec
module Integration
View
19 lib/spec/integration/dsl/caching.rb
@@ -0,0 +1,19 @@
+module Spec
+ module Integration
+ module DSL
+
+ # Provides a context within which view caching is enabled across
+ # requests made within the block.
+ #
+ def with_caching
+ Spec::Integration.ensure_caching_enabled
+ ActionController::Base.cache_store.reset
+ ActionController::Base.cache_store.perform_caching = true
+ yield
+ ensure
+ ActionController::Base.cache_store.perform_caching = false
+ end
+
+ end
+ end
+end
View
274 lib/spec/integration/dsl/form.rb
@@ -2,154 +2,156 @@ module Spec
module Integration
module DSL
- module FormExampleMethods
- # Makes assertions about the existance and validity of a form.
- # The _selector_ argument may be
- # <tt>a Symbol or String that is the form id</tt>
- # <tt>a String that is a valid CSS selector</tt>
- #
- def sees_form(selector, values, options = {})
- options = {
- :verify_field_enablment => false,
- :verify_field_values => true
- }.merge(options)
-
- selector = selector.to_s
- forms, tried_selector = nil, false
- forms = css_select "form##{selector}" if selector =~ /^[a-zA-Z_\-0-9]+$/
- if forms.blank?
- tried_selector = true
- forms = css_select selector
- end
+ # Makes assertions about the existance and validity of a form.
+ # The _selector_ argument may be
+ # <tt>a Symbol or String that is the form id</tt>
+ # <tt>a String that is a valid CSS selector</tt>
+ #
+ def sees_form(selector, values, options = {})
+ options = {
+ :verify_field_enablment => false,
+ :verify_field_values => true
+ }.merge(options)
+
+ selector = selector.to_s
+ forms, tried_selector = nil, false
+ forms = css_select "form##{selector}" if selector =~ /^[a-zA-Z_\-0-9]+$/
+ if forms.blank?
+ tried_selector = true
+ forms = css_select selector
+ end
- violated "Found no form having id or matching selector '#{selector}'" if forms.blank?
- if forms.size > 1
- violated "Found more than one form having id ##{selector}" if !tried_selector
- violated "Found more than one form matching selector '#{selector}'"
- end
+ violated "Found no form having id or matching selector '#{selector}'" if forms.blank?
+ if forms.size > 1
+ violated "Found more than one form having id ##{selector}" if !tried_selector
+ violated "Found more than one form matching selector '#{selector}'"
+ end
- form = forms[0]
- values.to_fields.each do |name, value|
- form_fields = css_select form, "input, select, textarea"
- matching_field = form_fields.detect {|field| field["name"] == name || field["name"] == "#{name}[]"}
- violated "Could not find a form field having the name '#{name}'" unless matching_field
- if options[:verify_field_values]
- matching_field["value"].should == value
- end
- if options[:verify_field_enablment] && matching_field["disabled"]
- violated "Form '#{selector}' has a field named '#{name}', but it is disabled. You may not submit values to it."
- end
- if matching_field["type"] == "file" && form["enctype"] != "multipart/form-data"
- violated "Form '#{selector}' has a file field named '#{name}', but the enctype is not multipart/form-data"
- end
- if matching_field.name == "select"
- should have_tag(matching_field, "option[value=#{value}]")
- end
+ form = forms[0]
+ form_fields = css_select form, "input, select, textarea"
+ values.to_fields.each do |name, value|
+ matching_field = form_fields.detect {|field| field["name"] == name || field["name"] == "#{name}[]"}
+ violated "Could not find a form field having the name '#{name}'" unless matching_field
+ if options[:verify_field_values]
+ matching_field["value"].should == value
+ end
+ if options[:verify_field_enablment] && matching_field["disabled"]
+ violated "Form '#{selector}' has a field named '#{name}', but it is disabled. You may not submit values to it."
+ end
+ if matching_field["type"] == "file" && form["enctype"] != "multipart/form-data"
+ violated "Form '#{selector}' has a file field named '#{name}', but the enctype is not multipart/form-data"
+ end
+ if matching_field.name == "select"
+ response.should have_tag(matching_field, "option[value=#{value}]")
end
- form
end
+ [form, form_fields]
+ end
- # Submit a form to the application after verifying it exists in the
- # current response body.
- #
- # If you are looking to use an alternate HTTP method, realize that the
- # intention is that you would generate your form appropriately to
- # include the hidden _method field. This will be handled by adding
- # that value to your form fields.
- #
- # Other hidden fields will also be added
- # unless you pass the option <tt>:include_hidden</tt> with the value
- # false. You may find this necessary when you have Arrays of Hashes -
- # they are sufficiently too complex to handle correctly in the
- # conversion to request parameters, merged with hiddens, and converted
- # back to the expected data structure.
- #
- # This method supports a couple of argument sequences:
- #
- # submit_form(selector = 'form', values = {}, options = {})
- # submit_form(values = {}, options = {})
- #
- # The former allows you to specify which form to submit when there are
- # multiple forms on a page. _selector_ may be the id of the form or a
- # valid CSS selector.
- #
- # The latter allows you to assume that there is only one form on the
- # page. It essentially defaults selector to the CSS selector 'form'.
- # Note that to pass options, you will need to use parens, as in:
- #
- # submit_form({}, :include_hidden => false)
- #
- # You CAN use ActionController::TestUploadFile's as parameters, thanks
- # to some work done by RubyRedRick!
- #
- # Supported options are:
- #
- # * <tt>:include_hidden</tt> - defaults to true. Hidden fields will be
- # included into your supplied params.
- # * <tt>:verify_field_enablment</tt> - will fail submission if a field
- # is disabled. Default is true.
- # * <tt>:verify_field_values</tt> - will fail submission if a field
- # does not have the provided value already. This is really mostly
- # useful when calling _sees_form_ directly. Default is false.
- #
- def submit_form(*args)
- selector = 'form'
- values = {}
- options = {
- :verify_field_enablment => true,
- :verify_field_values => false,
- :include_hidden => true
- }
-
- case args.size
- when 1
- if args.first.is_a?(Hash)
- values = args.first
- else
- selector = args.first
- end
- when 2
- if args.first.is_a?(Hash)
- values = args.first
- options.update(args.last)
- else
- selector, values = *args
- end
- when 3
- selector, values, = *args
+ # Submit a form to the application after verifying it exists in the
+ # current response body.
+ #
+ # If you are looking to use an alternate HTTP method, realize that the
+ # intention is that you would generate your form appropriately to
+ # include the hidden _method field. This will be handled by adding
+ # that value to your form fields.
+ #
+ # Other hidden fields will also be added
+ # unless you pass the option <tt>:include_hidden</tt> with the value
+ # false. You may find this necessary when you have Arrays of Hashes -
+ # they are sufficiently too complex to handle correctly in the
+ # conversion to request parameters, merged with hiddens, and converted
+ # back to the expected data structure.
+ #
+ # This method supports a couple of argument sequences:
+ #
+ # submit_form(selector = 'form', values = {}, options = {})
+ # submit_form(values = {}, options = {})
+ #
+ # The former allows you to specify which form to submit when there are
+ # multiple forms on a page. _selector_ may be the id of the form or a
+ # valid CSS selector.
+ #
+ # The latter allows you to assume that there is only one form on the
+ # page. It essentially defaults selector to the CSS selector 'form'.
+ # Note that to pass options, you will need to use parens, as in:
+ #
+ # submit_form({}, :include_hidden => false)
+ #
+ # You CAN use ActionController::TestUploadFile's as parameters, thanks
+ # to some work done by RubyRedRick!
+ #
+ # Supported options are:
+ #
+ # * <tt>:include_hidden</tt> - defaults to true. Hidden fields will be
+ # included into your supplied params.
+ # * <tt>:verify_field_enablment</tt> - will fail submission if a field
+ # is disabled. Default is true.
+ # * <tt>:verify_field_values</tt> - will fail submission if a field
+ # does not have the provided value already. This is really mostly
+ # useful when calling _sees_form_ directly. Default is false.
+ #
+ def submit_form(*args)
+ selector = 'form'
+ values = {}
+ options = {
+ :verify_field_enablment => true,
+ :verify_field_values => false,
+ :include_hidden => true
+ }
+
+ case args.size
+ when 1
+ if args.first.is_a?(Hash)
+ values = args.first
+ else
+ selector = args.first
+ end
+ when 2
+ if args.first.is_a?(Hash)
+ values = args.first
options.update(args.last)
+ else
+ selector, values = *args
end
-
- form = sees_form(selector, values, options)
- submit_to form["action"], load_hidden_fields(values, form, options[:include_hidden]), form["method"]
+ when 3
+ selector, values, = *args
+ options.update(args.last)
end
- private
- def load_hidden_fields(values, form, include_hidden = true)
- hiddens = css_select(form, "input[type=hidden]")
- return values if hiddens.blank?
-
- hidden_values = hiddens.inject([]) do |memo,h|
- memo << [h['name'], h['value']]; memo
- end
-
- given_values = values.to_fields
- form_method = hidden_values.assoc("_method")
- given_values << form_method if form_method
-
- if include_hidden
- given_values.each do |fieldvalue|
- fieldname = fieldvalue.first
- hidden_values.delete_if do |hiddenvalue|
- fieldname == hiddenvalue.first
- end
+ form, fields = sees_form(selector, values, options)
+ violated "Form '#{selector}' is missing an 'action' attribute" if form["action"].blank?
+ submit_to form["action"], collect_form_params(values, form, fields, options[:include_hidden]), form["method"], options
+ end
+
+ private
+ def collect_form_params(values, form, fields, include_hidden = true)
+ given_values = values.to_fields
+ overridden_array_field_names, string_form_params, file_form_params = [], [], []
+ fields.each do |field|
+ field_name = field['name']
+ next if overridden_array_field_names.include?(field_name)
+ submit = given_values.select {|k,v| k == field_name}
+ if submit.blank?
+ if (field['type'] == 'hidden' && include_hidden) || field_name == '_method'
+ submit = [field['name'], field['value']]
+ string_form_params << submit
+ end
+ else
+ if submit.size == 1 && ActionController::TestUploadedFile === submit.first.last
+ file_form_params.concat(submit)
+ else
+ overridden_array_field_names << field_name if field_name =~ /\[\]/
+ string_form_params.concat(submit)
end
- given_values.concat(hidden_values)
end
-
- ActionController::UrlEncodedPairParser.new(given_values).result
end
- end
+ form_params = Rack::Utils.parse_nested_query(string_form_params.collect {|k,v| "#{k}=#{CGI.escape(v.to_s)}"}.join('&'))
+ file_form_params.each do |k,v|
+ Rack::Utils.normalize_params(form_params, k, v)
+ end
+ form_params
+ end
end
end
View
86 lib/spec/integration/dsl/integration_example_group.rb
@@ -1,86 +0,0 @@
-module Spec
- module Integration
- module DSL
- include Spec::Integration::Matchers
- include NavigationExampleMethods
- include FormExampleMethods
-
- include ActionController::RecordIdentifier
-
- class IntegrationExample < Spec::Rails::Example::RailsExampleGroup # :nodoc:
- include Spec::Integration::DSL
- include ActionController::Integration::Runner
-
- def method_missing(sym, *args, &block)
- return Spec::Matchers::Be.new(sym, *args) if sym.starts_with?("be_")
- return Spec::Matchers::Has.new(sym, *args) if sym.starts_with?("have_")
- super
- end
-
- Spec::Example::ExampleGroupFactory.register(:integration, self)
- end
-
- end
- end
-end
-
-# 97% copied from RubyRedRick's patch at
-# http://dev.rubyonrails.org/attachment/ticket/11091/multi-part-integration.diff?format=raw
-ActionController::Integration::Session.class_eval do
- class MultiPartNeededException < Exception # :nodoc:
- end
-
- def process_with_multipart_upload(method, path, parameters = nil, headers = nil)
- process_without_multipart_upload(method, path, parameters, headers)
- rescue MultiPartNeededException
- boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
- status = process_without_multipart_upload(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
- return status
- end
- alias_method_chain :process, :multipart_upload
-
- def requestify_with_multipart_upload(parameters, prefix=nil)
- raise MultiPartNeededException if ::ActionController::TestUploadedFile === parameters
- requestify_without_multipart_upload(parameters, prefix)
- end
- alias_method_chain :requestify, :multipart_upload
-
- def multipart_requestify(params, first=true)
- returning p = {} do
- params.each do |key, value|
- k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
- if Hash === value
- multipart_requestify(value, false).each do |subkey, subvalue|
- p[k + subkey] = subvalue
- end
- else
- p[k] = value
- end
- end
- end
- end
-
- def multipart_body(params, boundary)
- multipart_requestify(params).map do |key, value|
- if value.respond_to?(:original_filename)
- File.open(value.path) do |f|
- <<-EOF
---#{boundary}\r
-Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r
-Content-Type: #{value.content_type}\r
-Content-Length: #{File.stat(value.path).size}\r
-\r
-#{f.read}\r
-EOF
- end
- else
-<<-EOF
---#{boundary}\r
-Content-Disposition: form-data; name="#{key}"\r
-\r
-#{value}\r
-EOF
- end
- end.join("")+"--#{boundary}--\r"
- end
-end
View
132 lib/spec/integration/dsl/navigation.rb
@@ -2,68 +2,98 @@ module Spec
module Integration
module DSL
- module NavigationExampleMethods
- # Uses css_select to retrieve the anchor having href_or_id. The
- # expects may indicate:
- #
- # Options are:
- #
- # * <tt>:count</tt> - An expression indicating the expected number of times the link can appear.
- # The default is '>= 1'.
- def find_anchor(href_or_id, expects = {})
- expects = {:count => ">= 1"}.update(expects)
- expects[:count] = "== #{expects[:count]}" if expects[:count].is_a? Integer
- if href_or_id =~ /\// # couldn't be an id with a slash in it
- links = css_select "a[href=#{href_or_id}]"
- violated "Expected #{expects[:count]} links to #{href_or_id}" unless eval("#{links.size} #{expects[:count]}")
- else
- links = css_select "a##{href_or_id}"
- violated "Expected only one link having id #{href_or_id}. Recall that id's must be unique in a DOM." unless links.size == 1
- end
- links[0]
+ # Uses css_select to retrieve the anchor having href_or_id. The
+ # expects may indicate:
+ #
+ # Options are:
+ #
+ # * <tt>:count</tt> - An expression indicating the expected number of times the link can appear.
+ # The default is '>= 1'.
+ def find_anchors(href_or_id, expects = {})
+ expects = {:count => ">= 1"}.update(expects)
+ expects[:count] = "== #{expects[:count]}" if expects[:count].is_a? Integer
+ if href_or_id =~ /\// # couldn't be an id with a slash in it
+ links = css_select "a[href=#{href_or_id}]"
+ violated "Expected #{expects[:count]} links to #{href_or_id}" unless eval("#{links.size} #{expects[:count]}")
+ else
+ links = css_select "a##{href_or_id}"
+ violated "Expected only one link having id #{href_or_id}. Recall that id's must be unique in a DOM." unless links.size == 1
end
+ links
+ end
+
+ # Clicks the link having either href or id equal to value of :link. If
+ # you don't care whether the link is actually on the page, try using
+ # _navigate_to_.
+ #
+ # Options are:
+ #
+ # * <tt>:link</tt> - href value or element id
+ # * <tt>:expects</tt> - {:count => <expression>}. See _find_anchors_.
+ # * <tt>:method</tt> - the preferred method to invoke, which assumes
+ # there is more than one link with the same href but different
+ # methods to invoke (like a show and delete link, which use the same
+ # href but have methods of GET and DELETE). Defaults to :get.
+ #
+ def click_on(options)
+ response.should have_navigated_successfully
- # Clicks the link having either href or id equal to value of :link. If
- # you don't care whether the link is actually on the page, try using
- # _navigate_to_.
- #
- # Options are:
- #
- # * <tt>:link</tt> - href value or element id
- # * <tt>:expects</tt> - {:count => <expression>}. See _find_anchor_.
- def click_on(options)
- should have_navigated_successfully
- options = {
- :link => nil,
- :expects => {:count => ">= 1"}
- }.update(options)
-
- anchor = find_anchor(options[:link], options[:expects])
+ options = {
+ :link => nil,
+ :expects => {:count => ">= 1"},
+ :method => :get
+ }.update(options)
+
+ href = nil
+ method = options.delete(:method)
+ anchors = find_anchors(options.delete(:link), options.delete(:expects))
+ if anchors.size == 1
+ anchor = anchors.first
if onclick = anchor["onclick"]
if onclick =~ /setAttribute\('name', '_method'\)/
onclick =~ /setAttribute\('value', '(get|put|delete|post)'\)/
- navigate_to anchor["href"], $1
+ href = anchor["href"]
+ method = $1
else
violated "There is some funky onclick on that link"
end
else
- navigate_to anchor["href"]
+ href = anchor["href"]
end
+ else
+ anchor = nil
+ anchors.each do |a|
+ onclick = a["onclick"]
+ if method == :get
+ anchor = a unless onclick
+ elsif onclick
+ if onclick =~ /setAttribute\('name', '_method'\)/
+ onclick =~ /setAttribute\('value', '(get|put|delete|post)'\)/
+ anchor = a if $1.to_s == method.to_s
+ else
+ violated "There is some funky onclick on that link: #{a["onclick"]}"
+ end
+ end
+ end
+ violated "No anchor found having method='#{method}'" if anchor.nil?
+ href = anchor["href"]
end
-
- # Performs _method_ on the specified path, ensuring that doing so was
- # successful. Will follow redirects.
- def navigate_to(path, method = :get, params = nil)
- self.send method, path, params || {}
- follow_redirect! while response.redirect?
- should have_navigated_successfully(path)
- end
-
- # Submits params to path, using the specified method - :post by
- # default
- def submit_to(path, params = {}, method = :post)
- navigate_to path, method, params
- end
+ navigate_to CGI.unescapeHTML(href), method, nil, options
+ end
+
+ # Performs _method_ on the specified path, ensuring that doing so was
+ # successful. Will follow redirects.
+ def navigate_to(path, method = :get, params = nil, options = {})
+ headers = options[:headers] || {}
+ self.send method, path, params || {}, headers
+ follow_redirect! while response.redirect?
+ response.should have_navigated_successfully(path)
+ end
+
+ # Submits params to path, using the specified method - :post by
+ # default
+ def submit_to(path, params = {}, method = :post, options = {})
+ navigate_to path, method, params, options
end
end
View
8 lib/spec/integration/extensions.rb
@@ -1,8 +0,0 @@
-Dir[File.dirname(__FILE__) + '/extensions/*'].each { |f| require f }
-
-module Spec
- module Integration
- module Extensions # :nodoc:
- end
- end
-end
View
13 lib/spec/integration/extensions/action_controller.rb
@@ -1,13 +0,0 @@
-# FIXME: this should be in a module that only gets loaded in integrationtests
-# This monkeypatch currently makes it impossible to create isolated controller specs
-# that pass through exceptions (see “Expecting Errors” on http://rspec.info/rdoc-rails/classes/Spec/Rails/Example/ControllerExampleGroup.html)
-ActionController::Base.class_eval do
- alias_method :rescue_action, :rescue_action_without_fast_errors
-
- attr_reader :rescued_exception
- def rescue_action_with_integration_support(e)
- @rescued_exception = e
- rescue_action_without_integration_support e
- end
- alias_method_chain :rescue_action, :integration_support
-end
View
27 lib/spec/integration/extensions/action_controller/base.rb
@@ -0,0 +1,27 @@
+module Spec
+ module Integration
+ module Extensions
+
+ # The point of all this is to simply capture the exceptions of an action
+ # during an integration testing request (get, post, put, etc). This
+ # exception is later used in the navigation matcher to show the failure
+ # if navigation did not succeed due to an exception.
+ #
+ module ActionController
+
+ module InstanceMethods #:nodoc:
+ attr_reader :rescued_exception
+ def rescue_action_locally(exception)
+ @rescued_exception = exception
+ super
+ end
+ end
+
+ end
+ end
+ end
+end
+
+ActionController::Base.module_eval do
+ include Spec::Integration::Extensions::ActionController::InstanceMethods
+end
View
111 lib/spec/integration/extensions/action_controller/caching.rb
@@ -0,0 +1,111 @@
+module Spec
+ module Integration
+ module Extensions
+ module ActionController
+ module Caching
+
+ module ClassMethods #:nodoc:
+ def cache_page(content, path)
+ test_page_cached << path
+ end
+
+ def expire_page(path)
+ test_page_expired << path
+ end
+
+ def cached?(path)
+ test_page_cached.include?(path)
+ end
+
+ def expired?(path)
+ test_page_expired.include?(path)
+ end
+
+ def reset_page_cache!
+ test_page_cached.clear
+ test_page_expired.clear
+ end
+ end
+
+ module InstanceMethods
+ # See if the page caching mechanism has cached a given url. This takes
+ # the same options as +url_for+.
+ def cached?(options = {})
+ self.class.cached?(test_cache_url(options))
+ end
+
+ # See if the page caching mechanism has expired a given url. This
+ # takes the same options as +url_for+.
+ def expired?(options = {})
+ self.class.expired?(test_cache_url(options))
+ end
+
+ private
+ def test_cache_url(options) #:nodoc:
+ url_for(options.merge({ :only_path => true, :skip_relative_url_root => true }))
+ end
+ end
+
+ class TestStore < ActiveSupport::Cache::Store
+ attr_accessor :perform_caching
+ attr_reader :expired, :expiration_patterns, :cache
+
+ def initialize(perform_caching = false) #:nodoc:
+ @perform_caching = perform_caching
+ @cache, @writes = {}, {}
+ @expired, @expiration_patterns = [], []
+ end
+
+ def reset
+ [@cache, @writes, @expired, @expiration_patterns].each(&:clear)
+ end
+
+ def read(name, options = nil) #:nodoc:
+ super
+ @cache[name] if perform_caching
+ end
+
+ def write(name, value, options = nil) #:nodoc:
+ super
+ @cache[name] = value if perform_caching
+ @writes[name] = options || {}
+ end
+
+ def delete(name, options = nil) #:nodoc:
+ super
+ @expired << name
+ end
+
+ def delete_matched(matcher, options = nil) #:nodoc:
+ super
+ @expiration_patterns << matcher
+ end
+
+ def cached?(name)
+ @writes.has_key?(name)
+ end
+
+ def expired?(name_or_matcher)
+ @expired.include?(name_or_matcher) || @expiration_patterns.detect { |matcher| name_or_matcher =~ matcher }
+ end
+
+ def writes(name)
+ @writes[name]
+ end
+ end
+
+ end
+ end
+ end
+ end
+end
+
+ActionController::Base.module_eval do
+ include Spec::Integration::Extensions::ActionController::Caching::InstanceMethods
+ extend Spec::Integration::Extensions::ActionController::Caching::ClassMethods
+
+ @@test_page_cached = [] # keep track of what gets cached
+ @@test_page_expired = [] # keeg track of what gets expired
+ cattr_accessor :test_page_cached
+ cattr_accessor :test_page_expired
+end
View
14 lib/spec/integration/extensions/spec/rails/example/integration_example_group.rb
@@ -0,0 +1,14 @@
+Spec::Rails::Example::IntegrationExampleGroup.module_eval do
+ include Spec::Integration::DSL
+ include Spec::Integration::Matchers
+ include ActionController::RecordIdentifier
+
+ # Override ActionController::Integration::Runner method_missing to keep
+ # RSpec be_ and have_ matchers working.
+ #
+ def method_missing(sym, *args, &block) # :nodoc:
+ return Spec::Matchers::Be.new(sym, *args) if sym.to_s.starts_with?("be_")
+ return has(sym, *args) if sym.to_s.starts_with?("have_")
+ super
+ end
+end
View
131 lib/spec/integration/matchers/caching.rb
@@ -0,0 +1,131 @@
+module Spec
+ module Integration
+ module Matchers
+
+ class CacheAction < Struct.new(:name, :store_options, :controller_context) #:nodoc:
+ def matches?(block)
+ ActionController::Base.cache_store.reset
+ block.call
+ @key = name.is_a?(String) ? name : controller_context.controller.fragment_cache_key(name)
+ @cached = ActionController::Base.cache_store.cached?(@key)
+ @options_match = ActionController::Base.cache_store.writes(@key) == store_options
+ @cached && @options_match
+ end
+
+ def failure_message
+ reason = if @cached && !@options_match
+ "the store options expected:\n #{store_options.inspect}\n" +
+ "do not match received:\n #{ActionController::Base.cache_store.writes(@key).inspect}"
+ elsif !@cached
+ if ActionController::Base.cache_store.cache.any?
+ "the cache only has #{ActionController::Base.cache_store.cache.to_yaml}."
+ else
+ "the cache is empty."
+ end
+ end
+ "Expected block to cache action #{name.inspect} (#{@key}), but #{reason}"
+ end
+
+ def negative_failure_message
+ "Expected block not to cache action #{name.inspect} (#{@key})"
+ end
+ end
+
+ # See if an action gets cached
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should cache_action(:index)
+ #
+ # You can pass in the name of an action which will then get
+ # interpreted in the context of the current controller. Alternatively,
+ # you can pass in a whole +Hash+ for +url_for+ defining all your
+ # paramaters.
+ def cache_action(action, store_options = {})
+ Spec::Integration.ensure_caching_enabled
+ action = { :action => action } unless action.is_a?(Hash)
+ CacheAction.new(action, store_options, self)
+ end
+
+ # See if a fragment gets cached.
+ #
+ # The name you pass in can be any name you have given your fragment.
+ # This would typically be a +String+.
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should cache('my_caching')
+ #
+ def cache(name)
+ CacheAction.new(name, self)
+ end
+ alias_method :cache_fragment, :cache
+
+ class ExpireAction #:nodoc:
+ def initialize(name, controller_context)
+ @name = name
+ @controller_context = controller_context
+ end
+
+ # Call the block of code passed to this matcher and see if
+ # our action has been removed from the cache.
+ #
+ # We determine the +fragment_cache_key+ here, taking the effort to
+ # pass in the controller to this class, because this method only
+ # works in the context of a request. Calling the block gives us that
+ # request.
+ def matches?(block)
+ ActionController::Base.cache_store.reset
+ block.call
+ @key = @name.is_a?(String) ? @name : @controller_context.controller.fragment_cache_key(@name)
+ return ActionController::Base.cache_store.expired?(@key)
+ end
+
+ def failure_message
+ reason = if ActionController::Base.cache_store.expired.any?
+ "the cache has only expired #{ActionController::Base.cache_store.expired.to_yaml}."
+ else
+ "nothing was expired."
+ end
+ "Expected block to expire action #{@name.inspect} (#{@key}), but #{reason}"
+ end
+
+ def negative_failure_message
+ "Expected block not to expire #{@name.inspect} (#{@key})"
+ end
+ end
+
+ # See if an action is expired
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should expire_action(:index)
+ #
+ # You can pass in the name of an action which will then get
+ # interpreted in the context of the current controller. Alternatively,
+ # you can pass in a whole +Hash+ for +url_for+ defining all your
+ # paramaters.
+ #
+ # This is a shortcut method to +expire+.
+ def expire_action(action)
+ action = { :action => action } unless action.is_a?(Hash)
+ expire(action)
+ end
+
+ # See if a fragment is expired
+ #
+ # The name you pass in can be any name you have given your fragment.
+ # This would typically be a +String+.
+ #
+ # Usage:
+ #
+ # lambda { get :index }.should expire('my_cached_something')
+ #
+ def expire(name)
+ ExpireAction.new(name, self)
+ end
+ alias_method :expire_fragment, :expire
+
+ end
+ end
+end
View
19 lib/spec/integration/matchers/display_object.rb
@@ -4,33 +4,38 @@ module Matchers
class DisplayObject # :nodoc:
include ActionController::RecordIdentifier
- def initialize(object, example)
- @object = object
+ def initialize(objects, example)
+ @objects = objects
@example = example
end
def matches?(response)
- response.should @example.have_tag("##{dom_id(@object)}")
+ @currently_matching = nil
+ @objects.each do |e|
+ @currently_matching = e
+ response.should @example.have_tag("##{dom_id(@currently_matching)}", :count => 1)
+ end
true
rescue
false
end
def failure_message
- "expected to find element having id #{dom_id(@object)} but none was found"
+ "expected to find element having id #{dom_id(@currently_matching)} but none was found"
end
def negative_failure_message
- "expected not to find element having id #{dom_id(@object)}"
+ "expected not to find elements having ids #{@objects.collect {|e| dom_id(e)}.inspect}"
end
end
# Specify that a response should be displaying an object according to
# the pattern of using _dom_id_.
#
- def display_object(object)
- DisplayObject.new(object, self)
+ def display_object(*objects)
+ DisplayObject.new(objects.flatten, self)
end
+ alias_method :display_objects, :display_object
end
end
end
View
35 lib/spec/integration/matchers/have_link_to.rb
@@ -0,0 +1,35 @@
+module Spec
+ module Integration
+ module Matchers
+ class HaveLinkTo # :nodoc:
+ include ActionController::RecordIdentifier
+
+ def initialize(href, example)
+ @href = href
+ @example = example
+ end
+
+ def matches?(response)
+ response.should @example.have_tag('a[href=?]', @href)
+ true
+ rescue
+ false
+ end
+
+ def failure_message
+ "expected to find a link to #{@href} but none was found"
+ end
+
+ def negative_failure_message
+ "expected not to find a link to #{@href}"
+ end
+ end
+
+ # Specify that a response should have a link with the specified href value.
+ #
+ def have_link_to(href)
+ HaveLinkTo.new(href, self)
+ end
+ end
+ end
+end
View
43 lib/spec/integration/matchers/navigate_successfully.rb
@@ -3,15 +3,15 @@ module Integration
module Matchers
class NavigateSuccessfully #:nodoc:
- def initialize(where)
- @where = where
+ def initialize(example, where)
+ @example, @where = example, where
end
- def matches?(example)
- if example.response.error? || example.response.body =~ /Exception caught/
- @failure_message = extract_exception(example)
- elsif example.response.missing?
- @failure_message = "Missing document #{example.request.method}'ing #{@where}"
+ def matches?(response)
+ if response.error? || response.body =~ /Exception caught/
+ @failure_message = extract_exception(@example)
+ elsif response.missing?
+ @failure_message = "Missing document #{@example.request.method}'ing #{@where}"
end
@failure_message.nil?
end
@@ -26,23 +26,32 @@ def negative_failure_message
private
def extract_exception(example)
- exception = example.controller.rescued_exception
- message = "Unexpected #{example.response.response_code} error #{example.request.method}'ing #{@where}\n#{exception.message}"
- if exception.respond_to? :line_number
- message << "\nOccurred on line #{exception.line_number} in #{exception.file_name}"
+ exception_in_controller_action = example.controller.rescued_exception
+ message = "Unexpected #{example.response.response_code} error #{example.request.method}'ing #{@where}"
+ if exception_in_controller_action
+ message << "\n#{exception_in_controller_action.message}"
+ if exception_in_controller_action.respond_to? :line_number
+ message << "\nOccurred on line #{exception_in_controller_action.line_number} in #{exception_in_controller_action.file_name}"
+ else
+ backtrace = Spec::Runner::QuietBacktraceTweaker.new.tweak_backtrace(exception_in_controller_action)
+ message << "\n#{backtrace * "\n"}"
+ end
else
- backtrace = Spec::Runner::QuietBacktraceTweaker.new.tweak_backtrace(example.controller.rescued_exception)
- message << "\n#{backtrace * "\n"}"
+ if example.response.body =~ %r{<h1>(.*?)</h1>\s*?<pre>(.*?)</pre>}mi
+ first, second = $1, $2
+ message << "\n\n#{first.gsub(/\s+/m, ' ').strip}\n\n#{second}"
+ end
+ message << "\n\n#{$1}" if example.response.body =~ %r{<div id="Full-Trace".*?>\s*?<pre><code>(.*?)</code></pre>\s*</div>}mi
end
+ message
end
end
# Specify that a response should be a good one: successful, not missing,
- # no server errors, etc. This is used internally by
- # Spec::Integration::DSL::NavigationExampleMethods, made available to
- # you for good pleasure.
+ # no server errors, etc.
+ #
def have_navigated_successfully(where = request.request_uri)
- NavigateSuccessfully.new(where)
+ NavigateSuccessfully.new(self, where)
end
end
View
2 spec/application.rb
@@ -1,2 +0,0 @@
-class ApplicationController < ActionController::Base
-end
View
7 spec/application_controller.rb
@@ -0,0 +1,7 @@
+ActionController::Base.session = {
+ :key => '_spec_integration_session',
+ :secret => '1ad3fa0f557c45019a3736577fa3fe5e'
+}
+
+class ApplicationController < ActionController::Base
+end
View
24 spec/dispatcher.rb
@@ -1,24 +0,0 @@
-#--
-# Copyright (c) 2004-2007 David Heinemeier Hansson
-#
-# 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.
-#++
-require 'action_controller/dispatcher'
-Dispatcher = ActionController::Dispatcher
View
28 spec/dsl/caching_spec.rb
@@ -0,0 +1,28 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe 'caches_action', :type => :integration do
+ it 'should work' do
+ time_one = Time.local(2009,1,1)
+ time_two = Time.local(2009,1,2)
+
+ Time.stub!(:now).and_return(time_two)
+ get '/caching_action'
+ response.body.should == time_two.to_s
+
+ with_caching do
+ # Let us ensure that it is not cached from outside this block
+ Time.stub!(:now).and_return(time_one)
+ get '/caching_action'
+ response.body.should == time_one.to_s
+
+ # And now it is cached here
+ Time.stub!(:now).and_return(time_two)
+ get '/caching_action'
+ response.body.should == time_one.to_s
+ end
+
+ # And now we should get fresh content
+ get '/caching_action'
+ response.body.should == time_two.to_s
+ end
+end
View
60 spec/dsl/form_spec.rb
@@ -1,41 +1,76 @@
-require File.dirname(__FILE__) + '/../spec_helper'
-require 'integration_dsl_controller'
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe "submit_form", :type => :integration do
+ it 'should submit the form' do
+ get '/form'
+ submit_form :key => 'value'
+ response.should_not be_nil
+ end
+end
describe "submit_form", :type => :controller do
include Spec::Integration::DSL
+ include Spec::Integration::Matchers
controller_name :integration_dsl
before do
response.stub!(:body).and_return %{
<form action="/order" id="order_form" method="get"></form>
<form action="/cancel" id="cancel_form" method="post"></form>
<form action="/repeat" class="special" method="post"></form>
+ <form action="/upload" id="upload_form" method="post" enctype="multipart/form-data">
+ <input type="file" name="myfile[inhere]" />
+ </form>
}
end
it "should find the form having the given id" do
- should_receive(:post).with("/cancel", {})
+ should_receive(:post).with("/cancel", {}, an_instance_of(Hash))
submit_form "cancel_form"
end
it 'should find the form matching the given css selector' do
- should_receive(:post).with("/repeat", {})
+ should_receive(:post).with("/repeat", {}, an_instance_of(Hash))
submit_form '.special'
end
it 'should use single form without having to specify' do
response.stub!(:body).and_return %{
<form action="/single" method="get"></form>
}
- should_receive(:get).with('/single', {})
+ should_receive(:get).with('/single', {}, an_instance_of(Hash))
submit_form
end
it "should use method of the rendered form" do
- should_receive(:get).with("/order", {})
+ should_receive(:get).with("/order", {}, an_instance_of(Hash))
submit_form "order_form"
end
+ it 'should use headers when provided' do
+ should_receive(:post).with("/cancel", {}, {:authorization => 'stuff'})
+ submit_form 'cancel_form', {}, :headers => {:authorization => 'stuff'}
+ end
+
+ it "should not disturb file field values" do
+ test_file = ActionController::TestUploadedFile.new(
+ File.dirname(__FILE__) + "/../spec.opts", "text/plain"
+ )
+ should_receive(:post).with("/upload", {"myfile" => {"inhere" => test_file}}, an_instance_of(Hash))
+ submit_form "upload_form", :myfile => {:inhere => test_file}
+ end
+
+ it 'should handle values that have special characters' do
+ response.stub!(:body).and_return %{
+ <form action="/special" method="get">
+ <input type="text" name="myfield" />
+ <input type="text" name="mynumber" />
+ </form>
+ }
+ should_receive(:get).with("/special", {'myfield' => "my;special\nstuff", 'mynumber' => '1'}, an_instance_of(Hash))
+ submit_form :myfield => "my;special\nstuff", :mynumber => 1
+ end
+
describe 'hidden fields' do
controller_name :integration_dsl
@@ -59,17 +94,17 @@
'overridden' => 'not_from_form'
}
}
- should_receive(:post).with("/hiddens", @expected)
+ should_receive(:post).with("/hiddens", @expected, an_instance_of(Hash))
end
it 'should be overridden when values are supplied' do
submit_form :overridden => 'not_from_form', :deeply => {:overridden => 'not_from_form'}
end
- it 'should remove in an array when overridden' do
- @expected['deeply'] = {'not_overridden' => ['value'], 'overridden' => 'from_form'}
+ it 'should submit only the values from the override when field is an array' do
+ @expected['deeply'] = {'not_overridden' => ['value1', 'value2'], 'overridden' => 'from_form'}
@expected['overridden'] = 'from_form'
- submit_form :deeply => {:not_overridden => ['value']}
+ submit_form :deeply => {:not_overridden => ['value1', 'value2']}
end
it 'should exclude all but _method when :include_hidden is false' do
@@ -99,9 +134,8 @@
end
def parse_as(expected)
- satisfy do |uri|
- actual = ActionController::AbstractRequest.parse_query_parameters(URI.escape(uri))
- actual == expected
+ simple_matcher(expected) do |uri|
+ Rack::Utils.parse_nested_query(URI.escape(uri)) == expected
end
end
end
View
6 spec/dsl/integration_spec_spec.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/../spec_helper'
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe "An integration spec", :type => :integration do
it "should provide all the normal integration support" do
@@ -16,11 +16,11 @@
end
it "should have the form dsl" do
- should respond_to("submit_form")
+ lambda { submit_form }.should_not raise_error(NoMethodError, /submit_form/)
end
it "should have the navigation dsl" do
- should respond_to("navigate_to")
+ lambda { navigate_to }.should_not raise_error(NoMethodError, /navigate_to/)
end
it "should have the showing matchers" do
View
41 spec/dsl/navigation_spec.rb
@@ -1,34 +1,47 @@
-require File.dirname(__FILE__) + '/../spec_helper'
-require 'integration_dsl_controller'
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-describe Spec::Integration::DSL, "find_anchor", :type => :controller do
+describe "find_anchors", :type => :controller do
+ include Spec::Integration::DSL
+ include Spec::Integration::Matchers
controller_name :integration_dsl
before do
response.stub!(:body).and_return %{<a href="/lala"></a>}
end
it "should find the anchor having the given href" do
- find_anchor('/lala').should_not be_nil
+ find_anchors('/lala').should_not be_nil
end
it "should violate when count is not as expected" do
lambda do
- find_anchor('/lala', :count => 0)
+ find_anchors('/lala', :count => 0)
end.should raise_error(Spec::Expectations::ExpectationNotMetError)
end
end
-describe Spec::Integration::DSL, "have_navigated_successfully", :type => :controller do
+describe "have_navigated_successfully", :type => :integration do
+ it "should report the exception in the failure message" do
+ get '/exploding'
+ lambda do
+ response.should have_navigated_successfully
+ end.should raise_error(Spec::Expectations::ExpectationNotMetError, /This will blow up!/)
+ end
+end
+
+describe 'click_on', :type => :controller do
+ include Spec::Integration::DSL
+ include Spec::Integration::Matchers
controller_name :integration_dsl
- it "should report the exception in the failure message" do
- with_routing do |set|; set.draw do |map|
- map.connect ':controller/:action/:id'
- get :exploding
- lambda do
- should have_navigated_successfully
- end.should fail_with(/This will blow up!/)
- end; end
+ before do
+ response.stub!(:body).and_return %{
+ <a href="/somewhere">Somewhere</a>
+ }
+ end
+
+ it 'should forward headers in the request' do
+ should_receive(:get).with('/somewhere', {}, {:authorization => 'stuff'})
+ click_on :link => '/somewhere', :headers => {:authorization => 'stuff'}
end
end
View
12 spec/environment.rb
@@ -1,12 +0,0 @@
-unless defined?(PLUGIN_ROOT)
- PLUGIN_ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
- RAILS_ROOT = PLUGIN_ROOT
- SUPPORT_TEMP = "#{PLUGIN_ROOT}/tmp"
- SUPPORT_LIB = "#{SUPPORT_TEMP}/lib"
- ACTIONPACK_ROOT = "#{SUPPORT_LIB}/actionpack"
- ACTIVESUPPORT_ROOT = "#{SUPPORT_LIB}/activesupport"
- ACTIVERECORD_ROOT = "#{SUPPORT_LIB}/activerecord"
- RSPEC_ROOT = "#{SUPPORT_LIB}/rspec"
- RSPEC_ON_RAILS_ROOT = "#{SUPPORT_LIB}/rspec_on_rails"
- SPEC_ROOT = "#{PLUGIN_ROOT}/spec"
-end
View
12 spec/integration_dsl_controller.rb
@@ -1,5 +1,15 @@
-class IntegrationDslController < ActionController::Base
+class IntegrationDslController < ApplicationController
+ caches_action :caching_action
+
+ def form
+ render :text => %{<form action="/form" method="post"><input type='hidden' name='key' /></form>}
+ end
+
def exploding
raise "This will blow up!"
end
+
+ def caching_action
+ render :text => Time.now.to_s
+ end
end
View
37 spec/matchers/caching_spec.rb
@@ -0,0 +1,37 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+class CachingController < ActionController::Base
+ caches_action :cache_store_params, :expires_in => 15.minutes
+ def cache_store_params
+ render :text => Time.now.to_s
+ end
+end
+
+describe 'cache_action', :type => :integration do
+ it 'should match when the action is cached' do
+ lambda do
+ get '/caching_action'
+ end.should cache_action(:caching_action)
+ end
+
+ it 'should not match when the action is not cached' do
+ lambda do
+ get '/'
+ end.should_not cache_action(:index)
+ end
+
+ it 'should work with url_for hash' do
+ lambda do
+ get '/'
+ end.should_not cache_action(:action => :index)
+ end
+
+ it 'should allow specifying the expected cache store params' do
+ lambda do
+ get '/caching/cache_store_params'
+ end.should cache_action({
+ :controller => 'caching',
+ :action => :cache_store_params
+ }, :expires_in => 15.minutes)
+ end
+end
View
53 spec/spec_helper.rb
@@ -1,29 +1,30 @@
-require File.dirname(__FILE__) + "/../spec/environment"
+SPEC_ROOT = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift(SPEC_ROOT) # for application_controller.rb
+
+RAILS_ROOT = File.expand_path("#{SPEC_ROOT}/..")
+$LOAD_PATH.unshift("#{RAILS_ROOT}/lib")
-unless defined? DATABASE_ADAPTER
- $: << "#{SPEC_ROOT}"
- $: << "#{PLUGIN_ROOT}/lib"
- $: << "#{RSPEC_ROOT}/lib"
- $: << "#{ACTIONPACK_ROOT}/lib"
- $: << "#{ACTIVERECORD_ROOT}/lib"
- $: << "#{ACTIVESUPPORT_ROOT}/lib"
- $: << "#{RSPEC_ON_RAILS_ROOT}/lib"
+require 'rubygems'
+require 'active_support'
+require 'active_record'
+require 'action_pack'
+require 'action_controller'
+require 'action_mailer'
- require 'active_support'
- require 'active_record'
- require 'action_controller'
- require 'action_view'
-
- require 'spec'
- require 'spec/rails'
- require 'spec/integration'
-
- def fail_with(message)
- raise_error(Spec::Expectations::ExpectationNotMetError, message)
+require 'rails/version'
+
+require 'spec'
+require 'spec/integration'
+require 'integration_dsl_controller'
+
+ActionController::Routing::Routes.draw do |map|
+ map.with_options :controller => 'integration_dsl' do |dsl|
+ dsl.root
+ dsl.connect '/caching_action', :action => 'caching_action'
+ dsl.connect '/exploding', :action => 'exploding'
+ dsl.connect '/form', :action => 'form'
end
-
- require 'logger'
- RAILS_DEFAULT_LOGGER = Logger.new("#{SUPPORT_TEMP}/test.log")
- RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
- ActiveRecord::Base.logger = RAILS_DEFAULT_LOGGER
-end
+ map.connect '/caching/cache_store_params',
+ :controller => 'caching', :action => 'cache_store_params'
+end
+
View
14 tasks/integration.rake
@@ -1,14 +0,0 @@
-# In rails 1.2, plugins aren't available in the path until they're loaded.
-# Check to see if the rspec plugin is installed first and require
-# it if it is. If not, use the gem version.
-rspec_base = File.expand_path(File.dirname(__FILE__) + '/../../rspec/lib')
-$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
-
-require 'spec/rake/spectask'
-
-namespace :spec do
- desc "Runs integration specs in spec/integration"
- Spec::Rake::SpecTask.new(:integration) do |t|
- t.spec_files = FileList["spec/integration/**/*_spec.rb"]
- end
-end

0 comments on commit 66bb06d

Please sign in to comment.