Thursday, April 15, 2021

Creating AWS resources in pytest fixtures with moto

 When unit testing functions that access AWS resources, the easiest way is to use the moto library. For instance, I have to test several functions that access an SQS queue, so I proceed this way:

import boto3
from moto import mock_sqs

def create_queue():
    client = boto3.client("sqs")
    queue_url = client.create_queue(QueueName="queue")["QueueUrl"]
    sqs = boto3.resource("sqs")
    return sqs.Queue(queue_url)

@mock_sqs
def test_something()
    queue = create_queue()
    ...

Since I had this create_queue() call at the beginning of most of my test functions, I wanted to make it a fixture. So i tried this way:

import boto3
from moto import mock_sqs
from pytest import fixture

@fixture
@mock_sqs
def queue():
    client = boto3.client("sqs")
    queue_url = client.create_queue(QueueName="queue")["QueueUrl"]
    sqs = boto3.resource("sqs")
    return sqs.Queue(queue_url)

@mock_sqs
def test_something(queue)
    ...

Unfortunately, this raised an error in my test functions stating that the queue does not exist. The reason for it is that the decorator @mock_sqs on the fixture would destroy my SQS queue as soon as it would leave the queue() method.

The solution is simple: do not use the mock as a decorator, but trigger it when the fixture is initializing and destroy it when it terminates. That means using a yield within the fixture to return the queue:

import boto3
from moto import mock_sqs
from pytest import fixture

@fixture
def queue():
    mock_sqs().start()

    client = boto3.client("sqs")
    queue_url = client.create_queue(QueueName="queue")["QueueUrl"]
    sqs = boto3.resource("sqs")
    yield sqs.Queue(queue_url)
    
    mock_sqs().stop()

def test_something(queue)
    ...

Notice that we have to drop the decorator on the test function as well.

Thanks for the answers to this moto issue.

No comments:

Post a Comment