Testing a Consumer - messagebus/lapine GitHub Wiki
When testing a consumer message hander, there are at least two options:
- trust Lapine, and unit test without RabbitMQ
- write integration tests that publish messages to RabbitMQ, then test the end results
For information on testing consumers, see Testing a Publisher
Writing unit tests
Consumer handlers must define a #handle_lapine_payload
method, which you can easily test without publishing messages to RabbitMQ.
class MyHandler
def self.handle_lapine_payload(payload, metadata)
MyHandlerWorker.perform_async(payload)
end
end
For this simple class, one could write a stubbed unit test:
RSpec.describe MyHandler do
describe 'handling lapine messages' do
before { allow(MyHandlerWorker).to receive(:perform_async) }
it 'passes off data to MyHandlerWorker' do
MyHandler.handle_lapine_payload({'omg' => 'lol'}, {})
expect(MyHandlerWorker).to have_received(:perform_async).with({'omg' => 'lol'})
end
end
end
Writing integration tests
When writing full integration tests that pass messages to RabbitMQ, the em-spec gem is useful for using EventMachine in your tests.
require 'spec_helper'
require 'amqp'
require 'em-spec/rspec'
require 'lapine/consumer'
RSpec.describe MyHandler do
include EM::SpecHelper
let(:runner) { Lapine::Consumer::Runner.new(argv) }
let(:argv) { %w(--config config/lapine.yml --transient --logfile /dev/null) }
before do
allow(MyHandlerWorker).to receive(:perform_async)
end
let(:payload) { {'omg' => 'lol'} }
let(:json) { payload.to_json }
it 'passes off data to MyHandlerWorker' do
em do
subject.run
EventMachine.add_timer(1.0) {
conn = Lapine::Consumer::Connection.new(runner.config, 'efrafa.topic')
conn.exchange.publish(json, routing_key: 'omg.lol')
}
EventMachine.add_timer(2.0) do
consumer.topology.close!
done
end
end
expect(MyHandlerWorker).to have_received(:perform_async).with(payload)
end
end
Take note of a few things:
Messages are published inside of an EventMachine timer. This is because inside the runner, queues are defined inside an EventMachine.run
block. If the messages are published in-line, there is a race condition where messages may be published before queues are registered. When this race condition is triggered, RabbitMQ will accept the message, see that there are no queues in which to write it, then throw it away. By publishing in a timer, we are waiting for the queues to be registered before we publish the message.
We stop EventMachine after a few seconds. Otherwise the run loop will never exit. Because of the potential for race conditions between the test thread and the runner's run loop, we need to wait for a while to ensure that the runner wakes up and does its work.
We configure the runner to throw away log messages. By default, the runner logs to STDOUT. It can be extremely annoying when unnecessary log data clutters up your test runs, so we tell Lapine to throw away log messages.
We pass --transient to the runner. This tells Lapine that any queues registered should be done so with auto_delete: true
. Otherwise queues will persist after the test completes.
We do not register a vhost or an exchange. That can be done outside of the test block, probably in a rake reset
task or something like it.