What's wrong with my pytest moto context in this test?

110 views Asked by At

As a frequent user of moto, I'm rather confused about the issue I'm facing. I set up a test class with a pytest fixture providing a DynamoDB table which I want to access in my test. Business as usual:

class TestDynamodbClient:

    @pytest.fixture
    def test_table(self):
        with mock_dynamodb():
            table_name = "test_table"

            dynamodb = boto3.resource('dynamodb')

            params = {
                'TableName': table_name,
                'KeySchema': [
                    {'AttributeName': 'id', 'KeyType': 'HASH'},
                ],
                'AttributeDefinitions': [
                    {'AttributeName': 'id', 'AttributeType': 'N'},
                ],
                'ProvisionedThroughput': {
                    'ReadCapacityUnits': 5,
                    'WriteCapacityUnits': 5
                }
            }

            table = dynamodb.create_table(**params)
            table.wait_until_exists()

            assert table_name in [t.name for t in dynamodb.tables.all()]  # this is to illustrate the setup works

            return table_name

Using the debugger, I confirmed the fixture to be evaluated prior to the test execution. In my test, however, the table does not exist:

    @pytest.mark.integration
    def test_recreate_table__table_exists__recreates_table(self, test_table):
        with mock_dynamodb():
            # given
            client = DynamodbClient()
    
            # this is to illustrate the setup does not work
            dynamodb = boto3.resource('dynamodb')
            assert test_table in [t.name for t in dynamodb.tables.all()]  # raises AssertionError: assert 'test_table' in []

            # when
            client.recreate_table("test_table")

            # then
            # ...

My conftest.py looks as follows:

def pytest_configure(config):
    """Mocked AWS Credentials for moto."""
    os.environ["AWS_ACCESS_KEY_ID"] = "testing"
    os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
    os.environ["AWS_SECURITY_TOKEN"] = "testing"
    os.environ["AWS_SESSION_TOKEN"] = "testing"
    os.environ["AWS_DEFAULT_REGION"] = "eu-central-1"
    os.environ["AWS_REGION"] = "eu-central-1"

    config.addinivalue_line(
        "markers",
        "integration: mark test to run only run as part of the integration test suite",
    )

I'm flabbergasted as to why the "test_table" table does not exist in my test, despite my fixture being evaluated. Does anyone have any pointers towards possible causes?

2

There are 2 answers

0
oschlueter On BEST ANSWER

The issue was caused by the second invocation of with mock_dynamodb() in the test:

@pytest.mark.integration
def test_recreate_table__table_exists__recreates_table(self, test_table):
    with mock_dynamodb():  # this seems to create a new moto 'context'
        # given
        client = DynamodbClient()

Upon removing the invocation, everything worked as intended.

1
Bert Blommers On

Because of the return, the fixture ends before the test_method starts. Because the fixture ends, the mock also ends - and Moto automatically clears any created resources at the end of the mock.

The test_table-fixture should yield instead:

@pytest.fixture
def test_table(self):
   with mock_dynamodb():
       table_name = "test_table"

       dynamodb = boto3.resource('dynamodb')

       # create table as normal...

       yield table_name