Gabriel Zuqueto Amaral
www.gabrielzuqueto.eti.br

RSpec Stub AWS SQS

Neste post montro como criar testes automáticos utilizando o RSpec, para o módulo SQS (Simple Queue Service) do AWS SDK v2. Confira Stub Aws SQS.

RSpec Stub AWS SQS

Decidi escrever o artigo RSpec Stub Aws SQS pois, a pouco tempo precisei criar testes para uma implementação que fiz utilizando o client SQS do Aws SDK v2, e não encontrei muita informação a respeito disso. Googlando, encontrei a documentação oficial da AWS que dá exemplos voltados para o Bucket S3, porém precisava criar stub para o SQS (Simple Queue Service). Continuei buscando e o que encontrei foram cópias da documentação oficial, logo não me ajudou.

Visando ajudar outros Devs, este post vai tratar da criação de testes automáticos para o módulo Aws::SQS do AWS SDK v2, utilizando o RSpec.

Lembrando que estamos utilizando o Ruby 2.1.8 e aws-sdk 2.9.7.

Montando o ambiente de teste

Crie um diretório para o nosso projeto teste:

mkdir rspec_aws_sdk_v2_sqs

Acesse o diretório criado e crie um Gemfile utilizando o bundle:

cd rspec_aws_sdk_v2_sqs
bundle --init

Adicione a gem do RSpec e do AWS SDK v2 no Gemfile:

source "https://rubygems.org"
gem 'aws-sdk', '~> 2'
gem 'rspec'

Caso queira debugar os teste, adicione também o byebug no Gemfile:

source "https://rubygems.org"
gem 'aws-sdk', '~> 2'
gem 'rspec'
gem 'byebug'

Após instale todas as gens utilizando o bundler:

bundle install

Agora crie a estrutura de diretório e arquivos padrão do RSpec:

rspec --init

Ao rodar o comando rspec –init, foram criados 2 arquivos e um diretório. No arquivo .rspec ficam as flags que são passadas para o RSpec quando a gente roda os testes. No diretório spec foi criado o arquivo spec_helper.rb, este arquivo contêm todas as configurações comuns entre os testes que criarmos. É dentro do diretório spec que devemos por os arquivos de teste.

Agora vamos criar o arquivo que irá conter nossos testes do módulo AWS::SQS.

touch spec/aws_sqs_spec.rb

Feito isso, podemos começar a criar os testes.

Habilitando o stub AWS SQS

Para permitir que o AWS SDK v2 aceite stubs, devemos utilizar a seguinte configuração:

Aws.config.update(stub_responses: true)

Ponha esta configuração antes do end, no final do arquivo spec_helper.rb. Não esqueça de fazer o require da gem da AWS.

require 'aws-sdk'

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups

  Aws.config.update(stub_responses: true)
end

A partir daí, já temos o ambiente pronto para o stub AWS.

Repare que removi todos os comentários que existiam neste arquivo.

Escrevendo o esqueleto do arquivo de testes

Abra o arquivo aws_sqs_spec.rb e ponha os seguintes comandos:

require 'spec_helper.rb'
describe "AWS::SQS" do

  let(:queue_url){"https://sqs.us-west-2.amazonaws.com/943154236803/gabrielzuqueto_eti_br"}
  let(:queue_name){:gabrielzuqueto_eti_br}

  before do
    @client_aws_sqs = Aws::SQS::Client.new
  end
end

Na primeira linha, a gente está carregando o arquivo de configuração do rspec no nosso teste.

require 'spec_helper.rb'

Após iniciamos o bloco geral dos nossos testes:

describe "AWS::SQS" do

end

Dentro do bloco, definimos 2 variáveis de instância, que vão nos ajudar a criar um padrão.

  let(:queue_url){"https://sqs.us-west-2.amazonaws.com/943154236803/gabrielzuqueto_eti_br"}
  let(:queue_name){:gabrielzuqueto_eti_br}

Daí, criamos um bloco que será executado, antes de cada teste posto dentro do bloco geral:

  before do
    @client_aws_sqs = Aws::SQS::Client.new
  end

Adicionamos dentro do bloco before, uma variável global chamada client_aws_sqs que recebe uma instância do Aws::SQS::Client.

A partir desta variável global, faremos nossos testes.

Stub AWS SQS Client create_queue

O método create_queue da classe Aws::SQS::Client, é reponsável por criar a fila. Este método aceita vários argumentos, porém vou me ater à criação padrão.

Lembrando que validações de caracteres no nome da fila, tamanho do nome, entre outros, não são validados pelo SDK e sim pelo servidor SQS, logo não conseguiremos testar estes tipos de validações.

  describe "When called create_queue" do
    it "Should error if queue_name is invalid" do
      expect { @client_aws_sqs.create_queue({queue_name: nil}) }.to raise_error(ArgumentError)
    end

    it "Should success" do
      @client_aws_sqs.stub_responses(:create_queue, queue_url: queue_url)
      response = @client_aws_sqs.create_queue({queue_name: queue_name})
      expect ( response.successful? ).should be_truthy
      expect ( response.queue_url ).should eq(queue_url)
    end
  end

No primeiro teste a gente garante que se for passado um valor nulo, um erro é retornado.

No segundo teste, a gente cria um stub para o Aws SQS Client create_queue, o qual retornará queue_url com o valor da nossa variável de instância, queue_url. Após a gente “cria” a fila e verifica se tudo o correu bem. E para finalizar, a gente confere se o valor de queue_url confere com o esperado.

Stub AWS SQS Client purge_queue

O método purge_queue da classe Aws::SQS::Client, é reponsável por limpar toda a fila sem excluí-la. Este método espera receber a URL da fila.

  describe "When called purge_queue" do
    it "Should return error if queue_url is invalid" do
      expect { @client_aws_sqs.purge_queue({queue_url: nil}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.purge_queue({queue_url: ""}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.purge_queue({queue_url: "something"}) }.to raise_error(ArgumentError)
    end

    it "Should return error if queue doesn't exists" do
      @client_aws_sqs.stub_responses(:purge_queue, 'NonExistentQueue')
      expect { @client_aws_sqs.purge_queue({queue_url: queue_url}) }.to raise_error(Aws::SQS::Errors::NonExistentQueue)
    end

    it "Should success" do
      response = @client_aws_sqs.purge_queue({queue_url: queue_url})
      expect ( response.successful? ).should be_truthy
      expect ( response ).should eq(Aws::EmptyStructure.new)
    end
  end

O primeiro teste garante que se queue_url não contiver uma URL válida, um erro será retornado.

O segundo teste garante que  se tentar limpar uma fila inexistente, um erro será retornado. Neste caso a gente criou um stub para purgue_queue que irá retornar o erro NonExistentQueue. Por isso a genre verifica no final se realmente este erro foi retornado.

O terceiro garante que se uma fila for purgada, será retornado sucesso. Além disso, a gente garante que a estrutura retornada é do tipo Aws::EmptyStructure como indiado na documentação.

Stub AWS SQS Client delete_queue

O método delete_queue da classe Aws:SQS:Client é responsável por eliminar uma fila, independente do conteúdo dela. Este método, como a maioria dos outros, espera o parâmetro queue_url.

  describe "When called delete_queue" do
    it "Should return error if queue_url is invalid" do
      expect { @client_aws_sqs.delete_queue({queue_url: nil}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_queue({queue_url: ""}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_queue({queue_url: "something"}) }.to raise_error(ArgumentError)
    end

    it "Should return error if queue doesn't exists" do
      @client_aws_sqs.stub_responses(:delete_queue, 'NonExistentQueue')
      expect { @client_aws_sqs.delete_queue({queue_url: queue_url}) }.to raise_error(Aws::SQS::Errors::NonExistentQueue)
    end

    it "Should success" do
      response = @client_aws_sqs.delete_queue({queue_url: queue_url})
      expect ( response.successful? ).should be_truthy
      expect ( response ).should eq(Aws::EmptyStructure.new)
    end
  end

Os testes do método delete_queue são bem parecidos com os do método purge_queue, logo dispensa explicação.

Stub AWS SQS Client get_queue_attributes

O método get_queue_attributes da classe Aws:SQS:Client é responsável por retornar os atributos solicitados, de uma fila. Como de praxe ele espera receber o parâmetro queue_url, além da lista de atributos que deverá ser retornada.

Este método é muito útil para sabermos o tamanho aproximado da fila, além de saber quantas mensagens estão in flight (o mesmo que indisponível para ser recuperada. Ela existe e poderá voltar para a fila, porém está “oculta” no momento).

  describe "When called get_queue_attributes" do
    it "Should return error if queue_name is invalid" do
      expect { @client_aws_sqs.delete_queue({queue_url: nil}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_queue({queue_url: ""}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_queue({queue_url: "something"}) }.to raise_error(ArgumentError)
    end

    describe "and expect returns successful" do
      before do
        @client_aws_sqs.stub_responses(:get_queue_attributes, attributes: {
          "ApproximateNumberOfMessages" => "10",
          "ApproximateNumberOfMessagesNotVisible" => "8",
          "ApproximateNumberOfMessagesDelayed" => "0"
        })
        @response = @client_aws_sqs.get_queue_attributes({queue_url: queue_url, attribute_names: [
          "ApproximateNumberOfMessages",
          "ApproximateNumberOfMessagesNotVisible",
          "ApproximateNumberOfMessagesDelayed"
        ]})
        expect ( @response.successful? ).should be_truthy
      end

      it "Should return the requested attributes - model 1" do
        expect ( @response.attributes.length ).should eq(3)
        expect ( @response.attributes["ApproximateNumberOfMessages"] ).should eq("10")
        expect ( @response.attributes["ApproximateNumberOfMessagesNotVisible"] ).should eq("8")
        expect ( @response.attributes["ApproximateNumberOfMessagesDelayed"] ).should eq("0")
      end

      it "Should return the requested attributes - model 2" do
        expected_response = Aws::SQS::Types::GetQueueAttributesResult.new(attributes: {
          "ApproximateNumberOfMessages"=>"10",
          "ApproximateNumberOfMessagesNotVisible"=>"8",
          "ApproximateNumberOfMessagesDelayed"=>"0"
        })
        expect ( @response ).should eq(expected_response)
      end
   end
  end

O primeiro teste é bem parecido com os outros… Mais uma vez estamos testando o parâmetro queue_url.

O segundo teste ficou mais complexo pois estamos utilizando duas formas de testar a mesma coisa. Vamos aos detalhes…

Primeiro criamos um novo escopo de testes dentro de um outro:

    describe "and expect returns successful" do

    end

A partir daí, pudemos criar um bloco que irá executar apenas quando os testes deste novo escopo forem executados:

      before do
        @client_aws_sqs.stub_responses(:get_queue_attributes, attributes: {
          "ApproximateNumberOfMessages" => "10",
          "ApproximateNumberOfMessagesNotVisible" => "8",
          "ApproximateNumberOfMessagesDelayed" => "0"
        })
        @response = @client_aws_sqs.get_queue_attributes({queue_url: queue_url, attribute_names: [
          "ApproximateNumberOfMessages",
          "ApproximateNumberOfMessagesNotVisible",
          "ApproximateNumberOfMessagesDelayed"
        ]})
        expect ( @response.successful? ).should be_truthy
      end

Este bloco cria o stub para o get_queue_attributes:

        @client_aws_sqs.stub_responses(:get_queue_attributes, attributes: {
          "ApproximateNumberOfMessages" => "10",
          "ApproximateNumberOfMessagesNotVisible" => "8",
          "ApproximateNumberOfMessagesDelayed" => "0"
        })

Após faz a solicitação dos atributos pelo Aws::SQS::Client e atribui o retorno a uma variável global, para que podemos testá-la nos próximos testes deste escopo:

        @response = @client_aws_sqs.get_queue_attributes({queue_url: queue_url, attribute_names: [
          "ApproximateNumberOfMessages",
          "ApproximateNumberOfMessagesNotVisible",
          "ApproximateNumberOfMessagesDelayed"
        ]})

Por fim, já faz a primeira verificação de sucesso:

        expect ( @response.successful? ).should be_truthy

No mesmo escopo “and expect returns successful”, criamos 2 modelos de teste que fazem a mesma coisa, sendo que um pode conter mais atributos, pois testamos apenas alguns:

      it "Should return the requested attributes - model 1" do
        expect ( @response.attributes.length ).should eq(3)
        expect ( @response.attributes["ApproximateNumberOfMessages"] ).should eq("10")
        expect ( @response.attributes["ApproximateNumberOfMessagesNotVisible"] ).should eq("8")
        expect ( @response.attributes["ApproximateNumberOfMessagesDelayed"] ).should eq("0")
      end

E o outro deve ser exatamente igual ao esperado:

      it "Should return the requested attributes - model 2" do
        expected_response = Aws::SQS::Types::GetQueueAttributesResult.new(attributes: {
          "ApproximateNumberOfMessages"=>"10",
          "ApproximateNumberOfMessagesNotVisible"=>"8",
          "ApproximateNumberOfMessagesDelayed"=>"0"
        })
        expect ( @response ).should eq(expected_response)
      end

Vale pensar bem antes de escolher o segundo modelo, pois é muito específico.

Stub AWS SQS Client get_queue_url

Todo o módulo Aws::SQS é baseado na URL da fila, logo faremos 2 testes cobrindo o método get_queue_url:

  describe "When called get_queue_url" do
    it "Should return NonExistentQueue error" do
      @client_aws_sqs.stub_responses(:get_queue_url, 'NonExistentQueue')
      expect { @client_aws_sqs.get_queue_url({queue_name: queue_name}) }.to raise_error(Aws::SQS::Errors::NonExistentQueue)
    end

    it "Should return queue URL" do
      @client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
      expect ( @client_aws_sqs.get_queue_url({queue_name: queue_name}).queue_url ).should eq(queue_url)
    end
  end

O primeiro item do teste de get_queue_url,testa se retorna erro caso a fila não exista.

    it "Should return NonExistentQueue error" do
      @client_aws_sqs.stub_responses(:get_queue_url, 'NonExistentQueue')
      expect { @client_aws_sqs.get_queue_url({queue_name: queue_name}) }.to raise_error(Aws::SQS::Errors::NonExistentQueue)
    end

O que fizemos foi dizer ao SQS que ao requisitar a URL da fila pelo método get_queue_url, deve retornar o erro de fila inexistente. Após a gente verifica com o expect, se realmente retornou o erro esperado.

Pode parecer sem sentido por estarmos testando puramente o SDK da Amazon, porém ao abstrair o módulo AWS::SQS e seus métodos através de uma classe, faz sentido testar o fluxo de quando uma fila não existe.

Note que estamos utilizando nossa variávelde instância: {queue_name: queue_name}

O segundo teste, testa se realmente a URL da fila foi retornada e se é a que esperamos.

    it "Should return queue URL" do
      @client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
      expect ( @client_aws_sqs.get_queue_url({queue_name: queue_name}).queue_url ).should eq(queue_url)
    end

Neste caso, pedimos ao SQS que retorne a propriedade queue_url, que terá o valor da variável de instância queue_url. Após a gente verifica se realmente o valor da propriedade queue_url é o valor que  a gente acabou de fazer stub.

Stub Aws SQS Client send_message

O método send_message espera no mínimo 2 parâmetros: queue_url e message_body. O primeiro parâmetro deve ser uma URL válida e o segundo não pode ser nulo. Logo faremos 3 testes para cobrir este método.

  describe "When called send_message" do
    it "Should return ArgumentError if queue_url is invalid URL" do
      expect { @client_aws_sqs.send_message({queue_url: "", message_body: "something" }) }.to raise_error(ArgumentError)
    end

    it "Should return ArgumentError if message_body is nil" do
      expect { @client_aws_sqs.send_message({queue_url: queue_url, message_body: nil }) }.to raise_error(ArgumentError)
    end

    it "Should return success" do
      expect ( @client_aws_sqs.send_message({queue_url: queue_url, message_body: "something" }).successful? ).should be_truthy
    end
  end

No primeiro teste a gente envia uma URL inválida e espera que dê erro de argumento inválido:

    it "Should return ArgumentError if queue_url is invalid URL" do
      expect { @client_aws_sqs.send_message({queue_url: "", message_body: "something" }) }.to raise_error(ArgumentError)
    end

No segundo teste,a gente envia uma mensagem inválida e espera que dê também o erro de argumento inválido:

    it "Should return ArgumentError if message_body is nil" do
      expect { @client_aws_sqs.send_message({queue_url: queue_url, message_body: nil }) }.to raise_error(ArgumentError)
    end

No terceiro teste, a gente envia uma mensagem para a fila e espera que seja entregue com sucesso:

    it "Should return success" do
      expect ( @client_aws_sqs.send_message({queue_url: queue_url, message_body: "something" }).successful? ).should be_truthy
    end

Stub Aws SQS Client receive_message

O método receive_message da classe Aws::SQS::Client é responsável por recuperar as mensagens da fila. Ele possui argumentos, porém vamos testar apenas o parâmetro max_number_of_messages, que por default é 1, ou seja, recupera apenas uma mensagem por vez, porém faremos um teste aumentando o valor deste parâmetro. Lembrando que o parâmetro queue_url é obrigatório.

  describe "When called receive_message" do
    it "Should return ArgumentError if queue_url is invalid URL" do
      expect { @client_aws_sqs.receive_message({queue_url: ""}) }.to raise_error(ArgumentError)
    end

    it "Should return one message" do
      @client_aws_sqs.stub_responses(:receive_message, messages: [body: "something"])
      response = @client_aws_sqs.receive_message({queue_url: queue_url})
      expect ( response.successful? ).should be_truthy
      expect ( response.messages.length ).should eq(1)
      expect ( response.messages.first.class).should eq(Aws::SQS::Types::Message)
      expect ( response.messages.first.body).should eq("something")
    end

    it "Should return two messages" do
      @client_aws_sqs.stub_responses(:receive_message, messages: [{body: "something"}, {body: "anything"}])
      response = @client_aws_sqs.receive_message({queue_url: queue_url, max_number_of_messages: 10})
      expect ( response.successful? ).should be_truthy
      expect ( response.messages.length ).should eq(2)
      expect ( response.messages.first.body).should eq("something")
      expect ( response.messages.last.body).should eq("anything")
    end

    it "Should return no messages" do
      @client_aws_sqs.stub_responses(:receive_message, messages: [])
      response = @client_aws_sqs.receive_message({queue_url: queue_url})
      expect ( response.successful? ).should be_truthy
      expect ( response.messages.length ).should eq(0)
    end
  end

O primeiro teste a gente já sabe que é o teste de URL inválida:

    it "Should return ArgumentError if queue_url is invalid URL" do
      expect { @client_aws_sqs.receive_message({queue_url: ""}) }.to raise_error(ArgumentError)
    end

O segundo teste a gente está testando o recebimento de uma mensagem. Para que o teste passe, as 4 condições devem ser satisfeitas, caso contrário o teste irá quebra:

    it "Should return one message" do
      @client_aws_sqs.stub_responses(:receive_message, messages: [body: "something"])
      response = @client_aws_sqs.receive_message({queue_url: queue_url})
      expect ( response.successful? ).should be_truthy
      expect ( response.messages.length ).should eq(1)
      expect ( response.messages.first.class).should eq(Aws::SQS::Types::Message)
      expect ( response.messages.first.body).should eq("something")
    end

A primeira condição verifica se recebemos a mensagem com sucesso.

A segunda condição verifica se recebemos apenas uma mensagem como esperávamos.

A terceira condição verifica se a classe da mensagem é a esperada (Aws::SQS::Types::Message).

A quarta condição verifica se o conteúdo da mensagem recebida é o esperado (something)

No terceiro teste, a gente verifica o recbimento de mais de uma mensagem. Para simular o real cenário, alteramos o parâmetro max_number_of_messages para 10 (máximo permitido pela AWS).

    it "Should return two messages" do
      @client_aws_sqs.stub_responses(:receive_message, messages: [{body: "something"}, {body: "anything"}])
      response = @client_aws_sqs.receive_message({queue_url: queue_url, max_number_of_messages: 10})
      expect ( response.successful? ).should be_truthy
      expect ( response.messages.length ).should eq(2)
      expect ( response.messages.first.body).should eq("something")
      expect ( response.messages.last.body).should eq("anything")
    end

Fizemos os mesmos testes do teste anterior, porém sem o teste da classe das mensagens e com o teste do conteúdo das duas mensagens.

Lembrando que no caso do stub de messages, poderíamos criar stub para os demais parâmetros como: message_id, receipt_handle, attributes, message_attributes…

Exemplo de Stub Aws SQS Client receive_message com message_id e receipt_hanlde:

# Stub receive_message com uma mensagem
      @client_aws_sqs.stub_responses(:receive_message, messages: [
        message_id: "Something",
        receipt_handle: "Something",
        body: "Something"
      ])
# Stub receive_message com mais de uma mensagem
      @client_aws_sqs.stub_responses(:receive_message, messages: [
        {
          message_id: "Something",
          receipt_handle: "Something",
          body: "Something"
        },
        {
          message_id: "Something_2",
          receipt_handle: "Something_2",
          body: "Something_2"
        }
      ])

No quarto teste a gente verifica o que acontece caso nenhuma mesagem seja recuperada, ou seja, caso a fila esteja vazia.

    it "Should return no messages" do
      @client_aws_sqs.stub_responses(:receive_message, messages: [])
      response = @client_aws_sqs.receive_message({queue_url: queue_url})
      expect ( response.successful? ).should be_truthy
      expect ( response.messages.length ).should eq(0)
    end

O retorno deve ser com sucesso porém sem nenhuma mensagem.

Stub Aws SQS Client delete_message

O método delete_message do módulo Aws::SQS::Client, espera receber 2 parâmetros: queue_url e receipt_handle. Este último a gente recebe do método receive_message. Logo, fica a dica: Não descarte o atributo receipt_handle que vem com cada uma das mensagens recuperadas.

  describe "When called delete_message" do
    it "Should return ArgumentError if queue_url is invalid URL" do
      expect { @client_aws_sqs.delete_message({queue_url: "", receipt_handle: "something"}) }.to raise_error(ArgumentError)
    end

    it "Should return ArgumentError if receipt_handle is invalid" do
      expect { @client_aws_sqs.delete_message({queue_url: queue_url, receipt_handle: nil}) }.to raise_error(ArgumentError)
    end

    it "Should return success" do
      response = @client_aws_sqs.delete_message({queue_url: queue_url, receipt_handle: "something"})
      expect ( response.successful? ).should be_truthy
      expect ( response ).should eq(Aws::EmptyStructure.new)
    end
  end

O primeiro teste já sabemos de cór o que é, então vou direto para o segundo.

No segundo teste a gente verifica que, se o receipt_handle for nulo, ocorre um erro.

No terceiro a gente verifica se ocorreu tudo bem ao excluir a mensagem. A última condição deste teste verifica se o retorno corresponde à classe esperada.

Stub Aws SQS Client delete_message_batch

O método delete_message_batch da classe Aws:SQS:Client, espera receber 2 parâmetros: queue_url e entries. O primeiro parâmetro não preciso mais explicar.

O segundo parâmetro é um Array que deve conter Hashs como esta:

{id: "message_id", receipt_handle: "receipt_handle"}

Diferente do delete_message, o delete_message_batch exige que a message_id esteja presente junto com o receipt_handle. Ambos os atributos desta hash vêm na mensagem recuperada pelo receive_message.

  describe "When called delete_message_batch" do
    it "Should return ArgumentError if queue_url is invalid URL" do
      expect { @client_aws_sqs.delete_message_batch({queue_url: "", entries: []}) }.to raise_error(ArgumentError)
    end

    it "Should return ArgumentError if entries is invalid" do
      expect { @client_aws_sqs.delete_message_batch({queue_url: queue_url, entries: nil}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_message_batch({queue_url: queue_url, entries: ""}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_message_batch({queue_url: queue_url, entries: [{id: nil, receipt_handle: "something"}]}) }.to raise_error(ArgumentError)
      expect { @client_aws_sqs.delete_message_batch({queue_url: queue_url, entries: [{id: "something", receipt_handle: nil}]}) }.to raise_error(ArgumentError)
    end

    it "Should return success" do
      expect ( @client_aws_sqs.delete_message_batch({queue_url: queue_url, entries: [{id: "something", receipt_handle: "something"}, {id: "anything", receipt_handle: "anything"}]}).successful? ).should be_truthy
    end
  end

O primeiro teste já é bem conhecido…

O segundo teste garante que, caso entries seja inválido, um erro estourará. Lembrando que se alguma das verificações falhar, o teste falha.

O terceiro, a gente garante que ao passar os parâmetros corretos, nenhum erro ocorre.

Stub Aws SQS QueuePoller

A classe QueuePoller do módulo Aws SQS é muito interessante pois permite que a gente mantenha uma conexão persistente com o SQS, e vá consumindo as mensagens de forma mais rápida, uma vez que a conexão será feita apenas uma vez.

Por padrão o QueuePoller elimina as mensagens recebidas, porém podemos previnir a exclusão com o seguinte parâmetro e valor: skip_delete: true.

Também é importante passar o parâmetro max_number_of_messages:10 para que o poller pegue o máximo de mensagens que puder, por tentativa e o wait_time_seconds: nil para que a conexão não seja fechada rápidamente, assim permitindo que a aplicação fique por um longo período de tempo conectada ao SQS.

Também não esqueça do parâmetro visibility_timeout pois este parâmetro recebe o valor em segundos, que cada mensagem firacá in flight.

Deixa de conversa e vamos ao teste!

  describe "When use poll" do
    before do
      @poller_aws_sqs = Aws::SQS::QueuePoller.new(queue_url, {max_number_of_messages:10, skip_delete: true, wait_time_seconds: nil, visibility_timeout: 60})
      @poller_aws_sqs.before_request { |stats| throw :stop_polling if stats.received_message_count >= 2 }
      @poller_aws_sqs.client.stub_responses(:receive_message, messages: [
        {
          message_id: "something",
          receipt_handle: "something",
          body: "something"
        },
        {
          message_id: "anything",
          receipt_handle: "anything",
          body: "anything"
        }
      ])
    end

    it "Should receive messages" do
      received_messages = []

      @poller_aws_sqs.poll do |messages, stats|
        messages.each do |message|
          received_messages << message
        end
      end
      
      expect ( received_messages.length ).should eq(2)
      expect ( received_messages.first.body ).should eq("something")
      expect ( received_messages.last.body ).should eq("anything")
    end
  end

Primeira coisa que fizemos foi instânciar o Aws::SQS::QueuePoller e atribuí-lo à uma variável global para que possamos utilizá-la no teste:

      @poller_aws_sqs = Aws::SQS::QueuePoller.new(queue_url, {max_number_of_messages:10, skip_delete: true, wait_time_seconds: nil, visibility_timeout: 60})

Após utilizamos o método before_request da classe Aws:SQS:QueuePoller para definirmos a condição de parada, pois sem ele, nosso teste ficaria em looping infnito, uma vez que configuramos o parâmetro wait_time_seconds: nil.

      @poller_aws_sqs.before_request { |stats| throw :stop_polling if stats.received_message_count >= 2 }

Com esta linha, dissemos ao Aws:SQS:QueuePoller para que encerre a conexão e saia do looping, assim que a quantidade de mensagens recebidas for maior ou igual a 2.

Depois criamos o stub do Aws::SQS::Client do Aws:SQS:QueuePoller, para que duas mensagens sejam retornadas quando o poll chamar o receive_message:

      @poller_aws_sqs.client.stub_responses(:receive_message, messages: [
        {
          message_id: "something",
          receipt_handle: "something",
          body: "something"
        },
        {
          message_id: "anything",
          receipt_handle: "anything",
          body: "anything"
        }
      ])

Daí declaramos uma variável para guardar as mensagens recebidas através do poll.

      received_messages = []

E iniciamos o poll:

      @poller_aws_sqs.poll do |messages, stats|
        messages.each do |message|
          received_messages << message
        end
      end

Basicamente ele entra no loop e ao receber um batch de mensagens, lê uma a uma e adiciona cada uma delas na variável received_messages.

Após testa a quantidade de mensagens recebidas e seus conteúdos:

      expect ( received_messages.length ).should eq(2)
      expect ( received_messages.first.body ).should eq("something")
      expect ( received_messages.last.body ).should eq("anything")

Com isso garantimos que o poller funcionou e que retornou as mensagens que criamos no stub.

Conclusão

Como podemos perceber, não é tão difícil fazer testes automáticos, utilizando o RSpec e criando stubs para testar as funcionalidades do módulo Aws::SQS, do AWS SDK v2.

Links

Código-fonte: https://github.com/gabrielzuqueto/rspec_aws_sdk_v2_sqs

Aws SQS Client: http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html

AwS SQS QueuePoller: http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/QueuePoller.html

No próximo post vou mostrar como criar uma classe para que possamos utilizar o módulo Aws::SQS de forma simples em nossos projetos. Também vou mostrar como criar testes para a classe que iremos criar. Dessa forma, tudo visto aqui neste post terá mais sentido.

https://gabrielzuqueto.eti.br/rspec-stub-aws-sqs 2017-04-16 02:40:56 -0300 gabrielzuqueto

Deixe seu comentário

Não perca mais nenhum post!

Cadastre-se e receba novos posts diretamente em seu e-mail.

Escolhidos para você

Abstrair AWS SQS no Ruby Parte 2: Criando testes automatizados
Abstrair AWS SQS no Ruby Parte 2: Criando testes automatizados

Abstrair AWS SQS no Ruby
Abstrair AWS SQS no Ruby

Como usar GitHub?
Como usar GitHub?