4

I am trying to mock an AWS SQS with moto, below is my code

from myClass import get_msg_from_sqs
from moto import mock_sqs
#from moto.sqs import mock_sqs


@mock_sqs
def test_get_all_msg_from_queue():
    
    #from myClass import get_msg_from_sqs
    
    conn = boto3.client('sqs', region_name='us-east-1')
    
    queue = conn.create_queue(QueueName='Test')
    
    os.environ["SQS_URL"] = queue["QueueUrl"]
    
    queue.send_message( MessageBody=json.dumps({'a': '1', 'b': '2', 'c': '3'}))
    
    
    #Tried this as well
    #conn.send_message(QueueUrl=queue["QueueUrl"], MessageBody=json.dumps({'a': '1', 'b': '2', 'c': '3'}))

    resp = get_msg_from_sqs(queue["QueueUrl"]) 

    assert resp is not None

While executing this I am getting the following error

>       queue.send_message( MessageBody=json.dumps({'a': '1', 'b': '2', 'c': '3'}))
E       AttributeError: 'dict' object has no attribute 'send_message'

If I try another way of to send a message in SQS(see commented out code #Tried this as well) then at the time of actual SQS calling in my method get_msg_from_sqs, I am getting the below error

E  botocore.exceptions.ClientError: An error occurred
(InvalidAddress) when calling the ReceiveMessage operation: 
The address https://queue.amazonaws.com/ is not valid for this endpoint.

I am running it on win10 with PyCharm and the moto version is set to

moto = "^2.2.6"

My code is given below

sqs = boto3.client('sqs')
def get_msg_from_queue(queue_url: str) -> dict:
    return sqs.receive_message(QueueUrl=queue_url, AttributeNames=['All'],
               MaxNumberOfMessages=1, VisibilityTimeout=3600, WaitTimeSeconds=0)

What am I missing over here?

Abhishek Patil
  • 1,373
  • 3
  • 30
  • 62
  • You're using mock here to test if the message read from the Queue is not None. Usually, there's a particular pattern you're looking for which you set and then assert for. Could you also add the code for get_msg_from_sqs ? – vivekveeramani Sep 16 '21 at 19:04
  • @vivekveeramani I have added the code for get_msg_from_sqs, hope this helps. – Abhishek Patil Sep 16 '21 at 19:37
  • As you can see in the docs, `create_queue` returns a dict, not a queue: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.create_queue – gshpychka Sep 21 '21 at 07:13
  • @gshpychka please have a look at the code again, queue is just the name of the variable that I have used. – Abhishek Patil Sep 21 '21 at 18:20
  • Well no, `queue` is a dict returned by `create_queue`: `queue = conn.create_queue(QueueName='Test')` – gshpychka Sep 21 '21 at 18:21
  • 1
    @gshpychka can you add the same thing as an answer, I would like to award the bounty to you – Abhishek Patil Sep 23 '21 at 17:46

2 Answers2

2

As per @gshpychka, you need to look at how create_queue works. Specifically it returns a dict of this form:

Response Structure (dict) --

QueueUrl (string) --

The URL of the created Amazon SQS queue.

So using this api you do:

import boto3
from time import sleep

conn = boto3.client('sqs')
queue = conn.create_queue(QueueName="Test")["QueueUrl"]
sleep(1) # wait at least 1s before using queue
response = conn.send_message(
    QueueUrl=queue,
    MessageBody='string',
    ...)

I agree that the docs are confusing. The confusion likely came about because of the sqs resource api, which works differently:

import boto3
from time import sleep

sqs = boto3.resource('sqs')
queue = sqs.create_queue(QueueName="Test2")
sleep(1)
queue.send_message(...)

This works because this api returns a Queue object, which is probably what you expected.

Please note that @gshpychka had already given the answer in a comment; I just wrote it out.

2e0byo
  • 5,305
  • 1
  • 6
  • 26
2

Your queue variable is a dict returned by create_queue:

queue = conn.create_queue(QueueName='Test')

It is not a queue and thus you cannot call sendMessage on it.

To do that, you need to create a queue object:

conn = boto3.client('sqs')
sqs = boto3.resource('sqs')
response = conn.create_queue(QueueName='Test')
queue_url = response["QueueUrl"]
queue = sqs.Queue(queue_url)

queue.send_message()
gshpychka
  • 8,523
  • 1
  • 11
  • 31
  • The example in this answer seems misleading or incomplete. It shows instantiation of a boto3 sqs resource, but then calls create_queue on a different object (presumably a boto3 sqs client, since if it were a sqs resource it would not return a dict, as assumed in the following line). If the conn object is an sqs client, the key for the URL would be "QueueUrl" not "QueueURL" (note capitalisation) – Ben Jul 26 '23 at 08:01
  • 1
    Yup, `conn` is an SQS client as per the code in the question. Thanks for the correction regarding the field name, updated the answer. – gshpychka Jul 26 '23 at 08:50