h2. About evented-spec Evented-spec is a set of helpers to help you test your asynchronous code. EventMachine/Cool.io-based code, including asynchronous "AMQP library":https://github.com/ruby-amqp/ruby-amqp is notoriously difficult to test. To the point that many people recommend using either "mocks":https://github.com/danielsdeleo/moqueue or "synchronous libraries":https://github.com/ruby-amqp/bunny instead of EM-based libraries in unit tests. This is not always an option, however -- sometimes your code just has to run inside the event loop, and you want to test a real thing, not just mocks. "em-spec":https://github.com/tmm1/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 state leak between examples and multiple mystereous failures. amqp-spec, and, subsequently, evented-spec add more helpers to keep your specs from being bloated. h2. Usage To get started with evented-spec you need to include one of the helper modules in your example groups, e.g.: bc. describe "eventmachine-based client" do include EventedSpec::SpecHelper it "should allow you to start a reactor" do em do EventMachine.reactor_running?.should be_true done end end context "nested contexts" do it "don't require another include" do em do EventMachine.add_timer(0.1) { @timer_run = true } done(0.3) end @timer_run.should be_true end end end Particular modules and methods are explained below. h3. #done We have no means to know when your work with reactor is finished, so whatever it is you need to call @done@ at some point. It optionally accepts a timeout and a block that is executed right before event reactor loop is stopped. If you don't call @done@, your specs are going to fail by timeout. h3. EventedSpec::SpecHelper @EventedSpec::SpecHelper@ is for semi-manual managing of reactor life-cycle. It includes three helpers: for EventMachine, Coolio and AMQP. @em@ stands for EventMachine. It takes a block, which is run after reactor starts. @amqp@ stands for AMQP. It takes a block, which is run after amqp connects with broker using given or default options. @coolio@ stands for cool.io. It takes a block, which is run after reactor starts. All three accept a hash of options. Look into method documentation to learn more. h3. EventedSpec::EMSpec, EventedSpec::AMQPSpec @EventedSpec::EMSpec@ wraps every example in em block, so it might save you a couple of lines per example. @EventedSpec::AMQPSpec@ wraps every example in amqp block. Also note that every example group including @EMSpec@ or @AMQPSpec@ automatically includes @SpecHelper@. Example: bc. describe "eventmachine specs" do include EventedSpec::EMSpec it "should run in a reactor" do EventMachine.reactor_running?.should be_true done # don't forget to finish your specs properly! end end h3. default_options, default_timeout You can also pass some default options to specs (like amqp settings), they're specific to domain you're using evented-spec in. @default_timeout@ sets time (in seconds) for specs to time out bc. describe "using default_timeout" do include EventedSpec::SpecHelper default_timeout 0.5 it "should prevent specs from hanging up" do em do 1.should == 1 # this spec is going to fail with timeout error because #done is not called end end end h2. Hooks There are 4 hooks available to evented specs: * @em_before@ -- launches after reactor started, before example runs * @em_after@ -- launches right before reactor is stopped, after example runs * @amqp_before@ -- launches after amqp connects, before example runs * @amqp_after@ -- launches before amqp disconnects, after example runs So, the order of hooks is as follows: @before(:all)@, @before(:each)@, @em_before@, @amqp_before@, example, @amqp_after@, @em_after@, @after(:each)@, @after(:all)@ bc. describe "using amqp hooks" do include EventedSpec::AMQPSpec default_timeout 0.5 amqp_before do AMQP.connection.should_not be_nil end let(:data) { "Test string" } it "should do something useful" do AMQP::Channel.new do |channel, _| exchange = channel.direct("amqp-test-exchange") queue = channel.queue("amqp-test-queue").bind(exchange) queue.subscribe do |hdr, msg| hdr.should be_an AMQP::Header msg.should == data done { queue.unsubscribe; queue.delete } end EM.add_timer(0.2) do exchange.publish data end end end end h2. AMQP gem compatibility AMQP spec helpers are for newer version of AMQP gem, 0.8. If you need spec-helpers for AMQP gem 0.7, take a look at "amqp-spec":https://github.com/ruby-amqp/amqp-spec, API is mostly the same. h2. Words of warning on blocking the reactor Evented specs are currently run inside of reactor thread. What this effectively means is that you *should not block* during spec execution. For example, the following *will not* work: bc. describe "using amqp" do include EventedSpec::AMQPSpec it "should do something useful" do channel = AMQP::Channel.new sleep 0.2 # voila, you're blocking the reactor channel.should be_open # no, it should not done end end What you *should* do instead is use callbacks: bc. describe "using amqp" do include EventedSpec::AMQPSpec it "should do something useful" do AMQP::Channel.new do |channel, _| channel.should be_open done end end end h2. See also You can see evented-spec in use in spec suites for our amqp gems, "amq-client":https://github.com/ruby-amqp/amq-client/tree/master/spec and "amqp":https://github.com/ruby-amqp/amqp/tree/master/spec. h2. Links * "cool.io":http://coolio.github.com/ * "amqp-spec":https://github.com/ruby-amqp/amqp-spec * "eventmachine":http://eventmachine.rubyforge.org/ * "amqp":https://github.com/ruby-amqp/amqp * "amq-client":https://github.com/ruby-amqp/amq-client