# TestingWithEventedSpec.textile

Path: | docs/TestingWithEventedSpec.textile
Last Update: | Wed Apr 17 10:45:57 +0000 2013

# @title Ruby AMQP gem: Testing AMQP applications

h1. Testing AMQP applications

h2. About this guide

This guide covers unit testing of amqp-based applications, primarily using "evented-spec":github.com/ruby-amqp/evented-spec.

h2. Covered versions

This guide covers "Ruby amqp gem":github.com/ruby-amqp/amqp v0.8.0 and later as well as "evented-spec gem":github.com/ruby-amqp/evented-spec v0.9.0 and later.

h2. Rationale

The AMQP protocol is inherently asynchronous. Testing of asynchronous code is often more difficult than synchronous code. There are two approaches to it:

- Stubbing out a big chunk of the environment
- Using the "real" environment

The former is risky because your application becomes divorced from actual behavior of other applications. The latter approach is more reliable but at the same time more tedious, because there is certain amount of incidental complexity that "real" environment carries.

However, a lot of this complexity can be eliminated with tools and libraries. The evented-spec gem is one of those tools. It grew out of necessity to test "amqp Ruby gem":github.com/ruby-amqp/amqp and has provent itself to be both very powerful and easy to use. This guide covers usage of that gem in context of applications that use amqp gem but can also be useful for testing EventMachine and Cool.io-based applications.

h2. Using evented-spec

h3. Setting up

To start using amqp all you need is to include `EventedSpec::AMQPSpec` module into your context and add `done` calls to your examples:

<script src="</script">gist.github.com/1027377.js"></script</a>>

h3. Testing in the Asynchronous Environment

Since we are using callback mechanisms in order to provide asynchronicity, we have to deal with situation when we expect a response, and response never comes. Usual solution includes setting a timeout which makes the given tests fail if they aren‘t finished in a timely manner. When `done` is called, your tests confirm successful ending of specs. Try removing `done` from the above example and see what happens. (spoiler: `EventedSpec::SpecHelper::SpecTimeoutExceededError: Example timed out`)

h3. The done method

The *done* method call is a hint for evented-spec to consider the example finished. If this method is not called, example will be forcefully terminated after a certain period of time or "time out". This means there are two approaches to testing of asynchronous code:

     * Have timeout value high enough for all operations to finish (for example, expected number of messages is received).
     * Call #done when some condition holds true (for example, message with a specific property or payload is received).

The latter approach is recommended because it makes tests less dependent on machine-specific throughput or timing: it is very common for continuous integration environments to use virtual machines that are significantly less powerful than machines developers use, so timeouts have to be carefully adjusted to work in both settings.

h3. Default Connection Options and Timeout

It is sometimes desirable to use custom connection settings for your test environment as well as the default timeout value used. evented-spec lets you do it:

<script src="gist.github.com/1027410.js"> </script>

Available options are passed to {AMQP.connect} so it is possible to specify host, port, vhost, username and password your test suite needs.

h3. Lifecycle Callbacks

evented-spec provides various callbacks similar to rspec‘s `before(:each)` / `after(:each)`. They are called `amqp_before` and `amqp_after` and happen right after connection is established or before connection is closed. It is a good place to put your channel initialization routines.

h3. Full Example

Now that you‘re filled on theory part, it‘s time to do something with all this knowledge. Below goes a slightly modified version of one of the integration specs from AMQP suite. It sets up default topic exchange and publishes various messages about sports events:

<script src="gist.github.com/1027478.js"> </script>

Couple of things to notice: `done` is invoked using an optional callback and optional delay, also instance variables behavior in hooks is the same as in "normal" rspec hooks.

h3. Using delayed

AMQP gem uses "EventMachine":eventmachine.rubyforge.org/ under hood. If you don‘t know about eventmachine, you can read more about it on the official site. What‘s important for us is that you *cannot use `sleep` for delays*. Why? Because all the specs code is processed directly in the "reactor":en.wikipedia.org/wiki/Reactor_pattern thread, if you `sleep` in that thread, reactor cannot send frames. What you need to use instead is `delayed` method. It takes delay time in seconds and callback which it launches once that time passes. Basic usage is either `sleep` replacement or ensuring certain order of execution (though, the latter should not bother you too much). You can also use it to cleanup your environment after tests if any is needed.

In the following example, we declare two channels, then declare the same queue twice with the same name but different options (which raises a channel-level exception in AMQP):

<script src="gist.github.com/1027500.js"> </script>

If you draw a timeline, various events happen at 0.0s, then at 0.1s, then at 0.3s and eventually at 0.4s.

h3. Design For Testability

As *Integration With Objects* section of the {file:docs/GettingStarted.textile Getting Started with Ruby amqp gem and RabbitMQ} demonstrates, good object-oriented design often makes it possible to test AMQP consumers in isolation without connecting to the broker or even starting EventMachine even loop. All the "Design for testability" practices apply fully to AMQP application testing.

h3. Real worldExamples

Please refer to the "amqp gem test suite":github.com/ruby-amqp/amqp/tree/master/spec to see evented-spec in action.

h3. How evented-spec Works

When you include `EventedSpec::AMQPSpec` module, `it` calls are wrapped in `EventMachine.start` + `AMQP.connect` calls, so you can start writing your examples as if you‘re connected. Please note that you still need to open your own channel(s).

h2. What to read next

There is a lot more to evented-spec than described in this guide. "evented-spec documentation":rdoc.info/github/ruby-amqp/evented-spec/master covers that gem in more detail gem. For more code examples, see "amqp Ruby gem test suite":github.com/ruby-amqp/amqp/tree/master/spec.

h2. Tell us what you think!

Please take a moment and tell us what you think about this guide "on Twitter":twitter.com/rubyamqp or "Ruby AMQP mailing list":groups.google.com/group/ruby-amqp: what was unclear? what wasn‘t covered? maybe you don‘t like guide style or grammar and spelling are incorrect? Readers feedback is key to making documentation better.

If mailing list communication is not an option for you for some reason, you can "contact guides author directly":michael@novemberain.com?subject=amqp%20gem%20documentation

<div id="disqus_thread"></div> <script type="text/javascript">

        /* * * CONFIGURATION VARIABLES * * */
        var disqus_shortname = 'rubyamqpdocs'; // required: replace example with your forum shortname

        var disqus_developer = 0; // set to 1 on local machine for testing comments
        var disqus_identifier = 'amqp_testing_with_evented_spec';
        var disqus_url = 'http://rdoc.info/github/ruby-amqp/amqp/master/file/docs/TestingWithEventedSpec.textile';

        /* * * DON'T EDIT BELOW THIS LINE * * */
        (function() {
            var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
            dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
        })();

</script>

[Validate]