Permalink
Browse files

Adds a helper class to start/stop Rack server for testing

Rather than having to write this same logic for the majority of
applications with which Kookaburra is used, pulling this code into
Kookaburra itself.
  • Loading branch information...
1 parent 497af6b commit c8d5badaddd13ccc0c6babeb6552a909ecca13ea @jwilger committed Mar 16, 2013
Showing with 147 additions and 91 deletions.
  1. +29 −65 README.markdown
  2. +110 −0 lib/kookaburra/rack_app_server.rb
  3. +8 −26 spec/integration/test_a_rack_application_spec.rb
View
@@ -115,22 +115,28 @@ and shut down a Rack application server. Just add the following to
`spec/support/kookaburra_setup.rb`:
require 'kookaburra/test_helpers'
- require 'thwait'
- require 'find_a_port' # from the find_a_port gem
+ require 'kookaburra/rack_app_server'
# Change these to the files that define your custom GivenDriver and UIDriver
# implementations.
require 'my_app/kookaburra/given_driver'
require 'my_app/kookaburra/ui_driver'
- APP_PORT = FindAPort.available_port
+ # `MyApplication` below should be replaced with the object that
+ # implements the Rack `#call` interface for your application. For a
+ # Rails app, this would be along the lines of
+ # `MyAppName::Application`.
+ app_server = Kookaburra::RackAppServer.new do
+ require 'path/to/my_application'
+ MyApplication
+ end
- # c.app_host below should be set to whatever the root URL of your running
- # application is.
+ # c.app_host below should be set to whatever the root URL of your
+ # running application is.
Kookaburra.configure do |c|
c.given_driver_class = MyApp::Kookaburra::GivenDriver
c.ui_driver_class = MyApp::Kookaburra::UIDriver
- c.app_host = 'http://localhost:%d' % APP_PORT
+ c.app_host = 'http://localhost:%d' % app_server.port
c.browser = Capybara::Session.new(:selenium)
c.server_error_detection { |browser|
browser.has_css?('head title', :text => 'Internal Server Error')
@@ -140,36 +146,12 @@ and shut down a Rack application server. Just add the following to
RSpec.configure do |c|
c.include(Kookaburra::TestHelpers, :type => :request)
- # Start the application server prior to running a group of integration
- # specs. `MyApplication` below should be replaced with the object that
- # implements the Rack `#call` interface for your application. For a Rails
- # app, this would be along the lines of `MyAppName::Application`.
c.before(:all, :type => :request) do
- # Run the server process in a forked process, and get a handle on that
- # process, so that it can be shut down after the tests run.
- #
- # Note that you cannot fork under JRuby and will need to use a thread.
- # See `spec/integration/test_a_rack_application_spec.rb` for an
- # example.
- @rack_server_pid = fork do
- Capybara.server_port = APP_PORT
- Capybara::Server.new(MyApplication).boot
-
- # This ensures that this forked process keeps running, because the
- # actual server is started in a thread by Capybara.
- ThreadsWait.all_waits(Thread.list)
- end
-
- # Give the server a chance to start up in the forked process. You may
- # need to adjust this depending on how long your application takes to
- # start up.
- sleep 1
+ app_server.boot
end
- # After the tests run, kill the server process.
c.after(:all, :type => :request) do
- Process.kill(9, @rack_server_pid)
- Process.wait
+ app_server.shutdown
end
end
@@ -214,22 +196,28 @@ and shut down a Rack application server. Just add the following to
`features/support/kookaburra_setup.rb`:
require 'kookaburra/test_helpers'
- require 'thwait'
- require 'find_a_port' # from the find_a_port gem
+ require 'kookaburra/rack_app_server'
# Change these to the files that define your custom GivenDriver and UIDriver
# implementations.
require 'my_app/kookaburra/given_driver'
require 'my_app/kookaburra/ui_driver'
- APP_PORT = FindAPort.available_port
+ # `MyApplication` below should be replaced with the object that
+ # implements the Rack `#call` interface for your application. For a
+ # Rails app, this would be along the lines of
+ # `MyAppName::Application`.
+ app_server = Kookaburra::RackAppServer.new do
+ require 'path/to/my_application'
+ MyApplication
+ end
- # c.app_host below should be set to whatever the root URL of your running
- # application is.
+ # c.app_host below should be set to whatever the root URL of your
+ # running application is.
Kookaburra.configure do |c|
c.given_driver_class = MyApp::Kookaburra::GivenDriver
c.ui_driver_class = MyApp::Kookaburra::UIDriver
- c.app_host = 'http://localhost:%d' % APP_PORT
+ c.app_host = 'http://localhost:%d' % app_server.port
c.browser = Capybara::Session.new(:selenium)
c.server_error_detection { |browser|
browser.has_css?('head title', :text => 'Internal Server Error')
@@ -238,34 +226,10 @@ and shut down a Rack application server. Just add the following to
World(Kookaburra::TestHelpers)
- # Start the application server prior to running the tests.
- # `MyApplication` below should be replaced with the object that
- # implements the Rack `#call` interface for your application. For a Rails
- # app, this would be along the lines of `MyAppName::Application`.
- # Runs the server process in a forked process, and get a handle on that
- # process, so that it can be shut down after the tests run.
- #
- # Note that you cannot fork under JRuby and will need to use a thread.
- # See `spec/integration/test_a_rack_application_spec.rb` for an
- # example.
- @rack_server_pid = fork do
- Capybara.server_port = APP_PORT
- Capybara::Server.new(MyApplication).boot
-
- # This ensures that this forked process keeps running, because the
- # actual server is started in a thread by Capybara.
- ThreadsWait.all_waits(Thread.list)
- end
-
- # Give the server a chance to start up in the forked process. You may
- # need to adjust this depending on how long your application takes to
- # start up.
- sleep 1
+ app_server.boot
- # After the tests run, kill the server process.
at_exit do
- Process.kill(9, @rack_server_pid)
- Process.wait
+ app_server.shutdown
end
## Defining Your Testing DSL ##
@@ -297,7 +261,7 @@ have the following scenario defined for an e-commerce application:
Feature: Purchase Items in Cart
Scenario: Using Existing Billing and Shipping Information
-
+
Given I have an existing account
And I have previously specified default payment options
And I have previously specified default shipping options
@@ -0,0 +1,110 @@
+require 'capybara'
+require 'thwait'
+require 'find_a_port'
+
+# Handles Starting/Stopping Rack Server for Tests
+#
+# `RackAppServer` is basically a wrapper around `Capybara::Server` that
+# makes it a bit easier to use Kookaburra with a Rack application (such
+# as Rails or Sinatra) when you want to run your tests locally against a
+# server that is only running for the duration of the tests. You simply
+# tell it how to get ahold of your Rack application (see `#initialize`)
+# and then call `#boot` before your tests run and `#shutdown` after your
+# tests run.
+#
+# @example using RSpec
+# # put this in something like `spec_helper.rb`
+# app_server = Kookaburra::RackAppServer.new do
+# # unless you are in JRuby, this stuff all runs in a new fork later
+# # on
+# ENV['RAILS_ENV'] = 'test'
+# require File.expand_path(File.join('..', '..', 'config', 'environment'), __FILE__)
+# MyAppName::Application
+# end
+# RSpec.configure do
+# c.before(:all) do
+# app_server.boot
+# end
+# c.after(:all) do
+# app_server.shutdown
+# end
+# end
+class Kookaburra::RackAppServer
+ attr_reader :port
+
+ # Sets up a new app server
+ #
+ # @param startup_timeout [Integer] (10) The maximum number of seconds
+ # to wait for the app server to respond
+ # @yieldreturn [#call] block must return a valid Rack application
+ def initialize(startup_timeout=10, &rack_app_initializer)
+ self.startup_timeout = startup_timeout
+ self.rack_app_initializer = rack_app_initializer
+ self.port = FindAPort.available_port
+ end
+
+ # Start the application server
+ #
+ # This will launch the server on a (detected to be) available port. It
+ # will then monitor that port and only return once the app server is
+ # responding (or after a 10 second timeout).
+ def boot
+ if defined?(JRUBY_VERSION)
+ thread_app_server
+ else
+ fork_app_server
+ end
+ wait_for_app_to_respond
+ end
+
+ def shutdown
+ return if defined?(JRUBY_VERSION)
+ Process.kill(9, rack_server_pid)
+ Process.wait
+ end
+
+ private
+
+ attr_accessor :rack_app_initializer, :rack_server_pid, :startup_timeout
+ attr_writer :port
+
+ def thread_app_server
+ Thread.new { start_server }
+ end
+
+ def fork_app_server
+ self.rack_server_pid = fork do
+ start_server
+ end
+ end
+
+ def start_server
+ app = rack_app_initializer.call
+ Capybara.server_port = port
+ Capybara::Server.new(app).boot
+ # This ensures that this forked process keeps running, because the
+ # actual server is started in a thread by Capybara.
+ ThreadsWait.all_waits(Thread.list)
+ end
+
+ def wait_for_app_to_respond
+ begin
+ Timeout.timeout(startup_timeout) do
+ next until running?
+ end
+ rescue Timeout::Error
+ raise "Application does not seem to be responding on port #{port}."
+ end
+ end
+
+ def running?
+ res = Net::HTTP.start('localhost', port) { |http| http.get('/__identify__') }
+ if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
+ true
+ else
+ false
+ end
+ rescue Errno::ECONNREFUSED, Errno::EBADF, Errno::ETIMEDOUT
+ false
+ end
+end
@@ -1,16 +1,12 @@
require 'kookaburra/test_helpers'
require 'kookaburra/api_driver'
+require 'kookaburra/rack_app_server'
require 'capybara'
-require 'thwait'
-require 'find_a_port'
# These are required for the Rack app used for testing
require 'sinatra/base'
require 'json'
-# The server port to which the application server will attach
-APP_PORT = FindAPort.available_port
-
describe "testing a Rack application with Kookaburra" do
describe "with an HTML interface" do
describe "with a JSON API" do
@@ -319,37 +315,23 @@ def delete_widget(name)
end
end
+ app_server = Kookaburra::RackAppServer.new do
+ JsonApiApp.new
+ end
+
before(:all) do
- start_server = lambda {
- Capybara.server_port = APP_PORT
- Capybara::Server.new(JsonApiApp.new).boot
- ThreadsWait.all_waits(Thread.list)
- }
- if defined?(JRUBY_VERSION)
- # Can't `fork` in JRuby. This doesn't provide the state
- # isolation that you get with forking (AFAIK, I'm no JVM
- # expert, though), but it lets the tests run and pass.
- Thread.new { start_server.call }
- else
- @rack_server_pid = fork do
- start_server.call
- end
- end
- sleep 1 # Give the server a chance to start up.
+ app_server.boot
end
after(:all) do
- unless defined?(JRUBY_VERSION)
- Process.kill(9, @rack_server_pid)
- Process.wait
- end
+ app_server.shutdown
end
it "runs the tests against the app" do
Kookaburra.configure do |c|
c.ui_driver_class = MyUIDriver
c.given_driver_class = MyGivenDriver
- c.app_host = 'http://127.0.0.1:%d' % APP_PORT
+ c.app_host = 'http://127.0.0.1:%d' % app_server.port
c.browser = Capybara::Session.new(:selenium)
c.server_error_detection do |browser|
browser.has_css?('head title', :text => 'Internal Server Error')

0 comments on commit c8d5bad

Please sign in to comment.