Integration Testing for TorqueBox
TorqueSpec
TorqueSpec extends the RSpec testing framework to provide integration testing for applications designed to run on the 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 capybara or 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 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 andpage
objects fromcapybara
so its require statemtent is guarded in aTorqueSpec.local
block, which won’t be run remotely, since thecapybara
won’t be available in the daemon’s runtime. Similarly, put statements that should only run remotely in aTorqueSpec.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 toTorqueBox::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?