4

I have a class which reads/processes messages from an SQS queue using the aws-sdk-rails gem (which is a wrapper on aws-sdk-ruby v2). How do I mock the AWS calls so I can test my code without hitting the external services?

communicator.rb:

class Communicator
  def consume_messages
    sqs_client = Aws::SQS::Client.new
    # consume messages until the queue is empty
    loop do
      r = sqs_client.receive_message({
                                              queue_url: "https://sqs.region.amazonaws.com/xxxxxxxxxxxx/foo",
                                              visibility_timeout: 1,
                                              max_number_of_messages: 1
                                     })
      break if (response.message.length == 0)
      # process r.messages.first.body
      r = sqs_client.delete_message({
                                      queue_url: "https://sqs.region.amazonaws.com/xxxxxxxxxxxx/foo",
                                      receipt_handle: r.messages.first.receipt_handle
                                    })
    end
  end
end
Pete
  • 10,310
  • 7
  • 53
  • 59
  • This older question addresses the same problem: http://stackoverflow.com/questions/16380426/how-to-mock-aws-sdk-gem. But well, it is about stubbing, not mocking. – Eric Platon Feb 29 '16 at 11:00

2 Answers2

11

The AWS SDK already provides stubbing. q.v. http://docs.aws.amazon.com/sdkforruby/api/Aws/ClientStubs.html for more information (Linked to official documentation.)

Rob Kinyon
  • 1,770
  • 2
  • 19
  • 28
10

I had a hard time finding examples mocking AWS resources. I spent a few days figuring it out and wanted to share my results on Stack Overflow for posterity. I used rspec-mocks (doubles & verifying doubles). Here's an example with the communicator.rb example in the question.

communicator_spec.rb:

RSpec.describe Communicator do
  describe "#consume_messages" do
    it "can use rspec doubles & verifying doubles to mock AWS SDK calls" do
      sqs_client = instance_double(Aws::SQS::Client)
      allow(Aws::SQS::Client).to receive(:new).and_return(sqs_client)
      SQSResponse = Struct.new(:messages)
      SQSMessage = Struct.new(:body, :receipt_handle)
      response = SQSResponse.new([SQSMessage.new(File.read('data/expected_body.json'), "receipt_handle")])
      empty_response = SQSResponse.new([])
      allow(sqs_client).to receive(:receive_message).
                            and_return(response, empty_response)
      allow(sqs_client).to receive(:delete_message).and_return(nil)

      Communicator.new.consume_messages
    end
  end
end
Pete
  • 10,310
  • 7
  • 53
  • 59
  • You can also do that with: `allow_any_instance_of(AWS::SQS::Client).to receive(:receive_message).and_return(response, empty_response) ` – jawspeak Jan 23 '17 at 02:58
  • After trying to overwrite the class, using `class_doubles`, and wayyyy too much time - this is the only way I could find that will not give you double leaking problems and all around just works. Thank you. – Dylan Pierce Apr 04 '17 at 19:15