Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Easy to use RSpec extensions for testing asynchronous EventMachine, Cool.io & AMQP applications
Ruby

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
lib
spec
tasks
.gitignore
Gemfile
HISTORY
LICENSE
README.rdoc
Rakefile
VERSION
amqp-spec.gemspec

README.rdoc

amqp-spec

by: Arvicco url: github.com/arvicco/amqp-spec

Summary

Simple API for testing asynchronous EventMachine/AMQP code.

Description

EventMachine-based code, including synchronous AMQP library is notoriously difficult to test. To the point that many people recommend using either Mocks or synchronous libraries instead of EM-based libraries in unit tests. This is not always an option, however - sometimes your code is supposed to run inside event loop, and you want to test a real thing, not mocks.

EM-Spec gem made it easier to write evented specs, but it has several drawbacks. First, it is not easy to manage both EM.run and AMQP.start loops at the same time. Second, AMQP is not properly stopped and deactivated upon exceptions and timeouts, resulting in AMQP library state leak between examples and multiple mystereous failures.

AMQP-Spec is built upon EM-Spec code but makes it easier to test AMQP event loops specifically. API is very similar to EM-Spec, only a bit extended. The final goal is to make writing AMQP specs reasonably pleasant experience and dispel the notion that evented AMQP-based libs are impossible to unit-test.

Mind you, you still have to properly manage your AMQP broker in order to prevent broker state from leaking between examples. You can try to combine AMQP-Spec and Moqueue if you want to abstract away actual broker interactions, but still specify some event-based expectations.

Rspec

There are several ways to use amqp-spec. To use it as a helper, include AMQP::SpecHelper in your describe block. You then use either 'amqp' or 'em' methods to wrap your evented test code. Inside the amqp/em block, you must call #done after your expectations. Everything works normally otherwise. You can use default_timeout and default_options macros to avoid manually setting AMQP options in each example. However, if you DO manually set options inside the example, they override the defaults. Only default options and default timeout are global across groups, it is currently impossible to have separate defaults for separate groups.

require "amqp-spec/rspec"
describe AMQP do
  include AMQP::SpecHelper

  default_options :host => 'my.amqp.broker.org', :port => '21118'
  # Can be used to set default options for all your amqp{} event loops

  default_timeout 1
  # Can be used to set default :spec_timeout for all your amqp-based specs

  before(:each) do
    puts EM.reactor_running?
  end

  it "works normally when not using #amqp or #em" do
    1.should == 1
  end

  it "makes testing evented code easy with #em" do
    em do
      start = Time.now

      EM.add_timer(0.5){
        (Time.now-start).should be_close( 0.5, 0.1 )
        done
      }
    end
  end

  it "runs AMQP.start loop with options given to #amqp" do
    amqp(:host => 'my.amqp.broker.org', :port => '21118')do
      AMQP.conn.should be_connected
      done
    end
  end

  it "optionally raises timeout exception if your loop hangs for some reason" do
    proc {
      amqp(:spec_timeout => 3){}
    }.should raise_error SpecTimeoutExceededError
  end

end

Another option is to include AMQP::Spec in your describe block. This will patch Rspec so that all of your examples run inside an amqp block automatically. A word of caution about before{} and after{} hooks in your example groups including AMQP::Spec. Each of these hooks will run in its separate EM loop that you'll need to shut down either manually (done) or via default_timeout. Essentially, this means that any EM-related state that you'd like to setup/teardown using these hooks will be lost as each example will run in a separate EM loop. In order to run setup/teardown hooks inside the EM loop, you'll need to use before_amqp{} and after_amqp{} hooks that run inside the EM loop but before/after AMQP loop (these hooks are currently not implemented)

require "amqp-spec/rspec"
describe AMQP do
  include AMQP::Spec

  before(:each) do
    puts EM.reactor_running?
    done
  end

  it "requires a call to #done in every example" do
    1.should == 1
    done
  end

  it "runs test code in an amqp block automatically" do
    start = Time.now

    EM.add_timer(0.5){
      (Time.now-start).should be_close( 0.5, 0.1 )
      done
    }
  end

  it "runs AMQP.start loop with default_options" do
    AMQP.conn.should be_connected
    done
  end

  it "raises timeout exception ONLY if default_timeout was set" do
    proc{}.should raise_error SpecTimeoutExceededError
  end
end

Finally, you can include AMQP::EMSpec in your describe block. This will run all the group examples inside em block instead of amqp. before{} and after{} hooks should be finished with 'done', same as when including AMQP::Spec, and same caution about using them applies.

require "amqp-spec/rspec"
describe AMQP do
  include AMQP::EMSpec

  it "requires a call to #done in every example" do
    1.should == 1
    done
  end

  it "runs test code in an amqp block automatically" do
    start = Time.now

    EM.add_timer(0.5){
      (Time.now-start).should be_close( 0.5, 0.1 )
      done
    }
  end
end

Bacon

Test::Unit

Limitations

AMQP-Spec can be currently used with rspec only. I suppose, there is nothing special in extending EM-Spec's test unit and bacon support, I just do not have experience dealing with these platforms. Another limitation, it uses native Fibers and therefore not compatible with Ruby 1.8. Again, it seems possible to rewrite it in 1.8-compatible style, with string evals and Fiber backport, but I'd rather leave this work to someone else.

Any help improving this library is greatly appreciated…

LICENSE:

Copyright © 2010 Arvicco. Original EM-Spec code copyright © 2008 Aman Gupta (tmm1)

See LICENSE for details.

Something went wrong with that request. Please try again.