torquebox / torquespec

Integration testing with TorqueBox
Apache License 2.0
17 stars 10 forks source link

+TITLE: Integration Testing for TorqueBox

+AUTHOR: Jim Crossley

+EMAIL: jcrossley@redhat.com

+LANGUAGE: en

+OPTIONS: H:3 num:nil toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t

+OPTIONS: TeX:t LaTeX:nil skip:nil d:nil todo:t pri:nil tags:not-in-toc

+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js

+EXPORT_SELECT_TAGS: export

+EXPORT_EXCLUDE_TAGS: noexport

** 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?