TorqueSpec
TorqueSpec extends the [[http://relishapp.com/rspec][RSpec testing framework]] to provide integration testing for applications designed to run on the [[http://torquebox.org/][TorqueBox Ruby application server]]. Before running your RSpec examples, TorqueSpec ensures that a TorqueBox server is running and enables you to verify expectations against real applications deployed on it.
Easy browser control/simulation is possible via the [[https://github.com/jnicklas/capybara][capybara]] or [[https://github.com/brynary/webrat][webrat]] gems, of course, but TorqueSpec also provides "in-container" testing, where your examples are actually run within the TorqueBox server itself.
** Installation
Install the torquespec gem:
: $ gem install torquespec
And then in your spec files (or indirectly via =spec_helper.rb=):
: require 'torquespec'
Doing this will cause TorqueSpec to add a "before suite" hook that fires up a TorqueBox server, i.e. JBoss, prior to running your specs and an "after suite" hook to shut it down.
** RSpec extensions
Aiming to be minimally intrusive, TorqueSpec makes two new methods available to your spec files: =deploy= and =remote_describe=.
*** deploy()
Determines which app[s] are deployed prior to running a particular
=ExampleGroup=, so typically called from a top-level =describe=
block. The deployment and subsequent undeployment occur within
"before all" and "after all" RSpec hooks, respectively.
The method accepts any number of arguments, each of which should
represent a [[http://torquebox.org/2x/builds/html-docs/deployment-descriptors.html][TorqueBox deployment descriptor]], typically a YAML
"here document", a Hash, or a path to an existing file. It also
accepts a block, the result of which should also be one or more
deployment descriptors. Here's an example using a single here
doc:
: describe "basic test with heredoc" do
:
: deploy <<-DD_END.gsub(/^ {4}/,'')
: application:
: root: #{File.dirname(__FILE__)}/../app
: env: development
: web:
: context: /basic
: DD_END
:
: it "should work" do
: visit "/basic"
: page.should have_content('it worked')
: page.find("#success")[:class].should == 'basic'
: end
:
: end
Here docs are especially nice because they support string
substitution. Note that we're setting the location of the app
relative to the location of the spec file itself,
e.g. =__FILE__=. And the little =gsub(/^ {4},'')= trick allows
you to indent the YAML relative to the Ruby.
*** remote_describe()
TorqueSpec supports "in container" testing via the
=remote_describe= method. It behaves exactly like the RSpec
=describe= method, but the resulting =ExampleGroup= will be run
inside the TorqueBox server, which is /remote/ relative to the
/local/ RSpec process.
If your spec calls =remote_describe=, TorqueSpec will deploy a
small daemon with your application. This daemon will act as an
additional RSpec runner to which the "local" runner will delegate
the invocation of those =ExampleGroups= defined by
=remote_describe=. This delegation occurs via DRb: the "remote"
runner reports its results to the "local" one, allowing you to
summarize the results of all your tests, both in-container and
out, in a single report.
Because your specs are consumed by two separate RSpec processes,
you must ensure that any gems other than TorqueSpec/RSpec are
available to the process that needs them.
: require 'torquespec'
: require 'torquebox-core'
:
: TorqueSpec.local {
: require 'capybara'
: }
:
: app = <<-END.gsub(/^ {4}/,'')
: application:
: root: #{File.dirname(__FILE__)}/../app
: END
:
: describe "local test" do
: deploy(app)
:
: it "should work" do
: visit "/"
: page.should have_content('it worked')
: end
: end
:
: remote_describe "remote test" do
: include TorqueBox::Injectors
: deploy(app)
:
: it "should work" do
: some_service = fetch(com.foo.SomeService)
: some_service.run.should == 'SUCCESS'
: end
: end
The above spec shows two example groups, one local and one remote.
There are a few things to note:
- This spec will be processed by both the local test runner and
the remote daemon.
- The local example needs the =visit()= method and =page=
objects from =capybara= so its require statemtent is guarded
in a =TorqueSpec.local= block, which won't be run remotely,
since the =capybara= won't be available in the daemon's
runtime. Similarly, put statements that should *only* run
remotely in a =TorqueSpec.remote= block.
- TorqueBox injection is supported in remote examples as long as
=TorqueBox::Injectors= is included in their group.
- Because the =remote_describe= block is evaluated locally (but
not executed), we must require 'torquebox-core' to refer to
=TorqueBox::Injectors= both locally and remotely, even though
the injection will only be performed remotely.
*** Nesting Example Groups
The above example is not very efficient because the same
application is being deployed twice: once for the local =describe=
and again for the =remote_describe=. We can do better by taking
advantage of RSpec's support for nested groups:
: describe "outside the container" do
:
: before(:each) do
: puts "runs both locally and remotely"
: end
:
: deploy <<-END.gsub(/^ {4}/,'')
: application:
: root: #{File.dirname(__FILE__)}/../app
: END
:
: it "should handle web requests" do
: visit "/"
: end
:
: remote_describe "inside the container" do
:
: before(:each) do
: puts "runs only remotely"
: end
:
: it "should handle injection" do
: fetch( 'service-registry' ).should_not be_nil
: end
:
: end
:
: end
Example groups may be arbitrarily nested, but remember that *ALL*
subgroups of a =remote_describe= will be executed remotely by the
TorqueSpec daemon.
Also note that =before= and =after= blocks should work as you
expect, but the fact that the parent's =before= block in the above
example will run remotely might mean you'll need to guard certain
statements in either a =TorqueSpec.local= or =TorqueSpec.remote=
block.
** Configuration
Configuration parameters are variables in the TorqueSpec namespace, e.g. =TorqueSpec.max_heap=.
| Parameter | Description | Default | |--------------+----------------------------------------------------------------+---------------| | ~knob_root~ | Where TorqueSpec creates your deployment descriptors | .torquespec | | ~jboss_home~ | Where JBoss is installed | ~$JBOSS_HOME~ | | ~max_heap~ | The maximum RAM allocated to the JBoss JVM | 1024 MB | | ~jvm_args~ | Arguments to the JVM running JBoss | /see below/ | | ~drb_port~ | The in-container spec runner listens on this port for requests | 7772 | | ~lazy~ | Whether to use a running JBoss and leave it running when done | false | | ~default_deploy~ | Where inside of ~knob_root~ to check for an optional default deployment descriptor | default_deploy.yml |
By default, TorqueSpec is initialized thusly:
: TorqueSpec.configure do |config| : config.drb_port = 7772 : config.knob_root = ".torquespec" : config.jvm_args = "-Xms64m -Xmx1024m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSClassUnloadingEnabled -Dgem.path=default" : end
Include a similar block in your =spec_helper.rb= to customize any of these.
** Dependencies
TorqueSpec has been extensively tested with RSpec 2, though RSpec 1 should be compatible as well. But really, why are you still using RSpec 1?