0
votes

I am writing a unit test for a python function that does a update_item in dynamodb via the table resource. The normal unit test works but raising a ClientError is something I cannot ge to work..

The tests get executed but I alwys: E Failed: DID NOT RAISE <class 'botocore.exceptions.ClientError'>

What I am trying to do is simulate the case when I would get a Internal Server Error of AWS or a RequestLimitExceeded error.

Thanks for pointing me in the correct direction!

My functional code:

def _updateCardConfiguration(identifier, cardType, layout):
    try:
        table.update_item(
            Key={
                'identifier': identifier,
                'cardType': cardType
            },
            UpdateExpression="SET layout = :layout",
            ExpressionAttributeValues={
                ':layout': layout
            }
        )

    except ClientError as e:
        logger.fatal(
            f"The update of the cardconfiguration for the niche: {identifier} has failed with the following message: {e.response['Error']['Message']}")
        raise

    return True


def lambda_handler(event, context):
    # Check the cardtype
    cardType = 'SearchresultCard' if (
        event['detail']['action'] == 'updateSearchresultCard') else 'DetailCard'

    # Update the card configuration
    _updateCardConfiguration(
        event['detail']['identifier'], cardType, event['detail']["layout"])


    return True

My test code:

@pytest.fixture(scope='function')
def cardConfigurationsTable(aws_credentials):
    with mock_dynamodb2():
        #client = boto3.client('dynamodb', region_name='eu-west-1')
        dynamodb = boto3.resource('dynamodb')
        dynamodb.create_table(
            TableName='CardConfigurations',
            KeySchema=[
                {
                    'AttributeName': 'identifier',
                    'KeyType': 'HASH'
                },
                {
                    'AttributeName': 'cardType',
                    'KeyType': 'RANGE'
                }
            ],
            AttributeDefinitions=[
                {
                    'AttributeName': 'identifier',
                    'AttributeType': 'S'
                },
                {
                    'AttributeName': 'cardType',
                    'AttributeType': 'S'
                },
            ],
            StreamSpecification={
                'StreamEnabled': True,
                'StreamViewType': 'NEW_AND_OLD_IMAGES'
            },
            BillingMode='PAY_PER_REQUEST',
            SSESpecification={
                'Enabled': True
            },
            GlobalSecondaryIndexes=[
                {
                    'IndexName': 'byIdentifier',
                    'KeySchema': [
                        {
                            'AttributeName': 'identifier',
                            'KeyType': 'HASH'
                        },
                        {
                            'AttributeName': 'cardType',
                            'KeyType': 'RANGE'
                        }
                    ],
                    'Projection': {
                        'ProjectionType': 'ALL'
                    }
                }
            ])

        yield dynamodb.Table('CardConfigurations')

def test_updateItem_raises_clientExecption(cardConfigurationsTable, env_vars):
    """ Unit test for testing the lambda that updates the cardconfiguration

    Scenario: No detail card is configured for the niche

      :param fixture cardConfigurationsTable: The fixture that mocks the dynamodb CardConfigurations table
      :param fixture env_vars: The fixture with the env vars
    """
    # Prepare the event
    with open('tests/unit/event-flows/dataconfiguration-state-transition/events/updateSearchresultCard.json') as json_file:
        event = json.load(json_file)

    stubber = Stubber(cardConfigurationsTable.meta.client)
    stubber.add_client_error(
        "update_item",
        service_error_code="InternalServerError",
        service_message="Internal Server Error",
        http_status_code=500,
    )
    stubber.activate()

    # Import the lambda handler
    lambdaFunction = importlib.import_module(
        "event-flows.dataconfiguration-state-transition.updateCardconfigurationInDb.handler")

    with pytest.raises(ClientError) as e_info:
        # Execute the handler
        response = lambdaFunction.lambda_handler(event, {})

1

1 Answers

0
votes

I would start by creating a default table that your _updateCardConfiguration function can use.

def get_table():
    table = dynamodb.Table("CardConfigurations")
    return table


default_table = get_table()

You can then pass it as a default argument to the function that needs it. This makes it easier to test the function. To do so, modify your _updateCardConfiguration function header to look like this :

def _updateCardConfiguration(identifier, cardType, layout, table=default_table):

Doing this gives us the ability to replace the default table with a mocked table for testing. The complete modified function looks like this:

def _updateCardConfiguration(identifier, cardType, layout, table=default_table):
    try:
        table.update_item(
            Key={"identifier": identifier, "cardType": cardType},
            UpdateExpression="SET layout = :layout",
            ExpressionAttributeValues={":layout": layout},
        )

    except ClientError as e:
        logger.fatal(
            f"The update of the cardconfiguration for the niche: {identifier} has failed with the following message: {e.response['Error']['Message']}"
        )
        raise

    return True

As you can see, all that changes is the header.

Then in your test_updateItem_raises_clientExecption test function file, I would change the lines

 with pytest.raises(ClientError) as e_info:
    # Execute the handler
    response = lambdaFunction.lambda_handler(event, {})

to:

with pytest.raises(ClientError, match="InternalServerError"):
    # Execute the _updateCardConfiguration function
    lambdaFunction._updateCardConfiguration(
        None, None, None, cardConfigurationsTable
    )

By passing in your cardConfigurationsTable fixture, your function wiil operate against it and you should get the behaviour you expect.