0

I have this python script snippet with mocked aws infrastructure for a todo list application:

import os
import boto3
import time
import uuid
import json
import functools
from botocore.exceptions import ClientError


def get_table(dynamodb=None):
    if not dynamodb:
        URL = os.environ['ENDPOINT_OVERRIDE']
        if URL:
            print('URL dynamoDB:'+URL)
            boto3.client = functools.partial(boto3.client, endpoint_url=URL)
            boto3.resource = functools.partial(boto3.resource,
                                               endpoint_url=URL)
        dynamodb = boto3.resource("dynamodb")
    # fetch todo from the database
    table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])
    return table

def put_item(text, dynamodb=None):
    table = get_table(dynamodb)
    timestamp = str(time.time())
    print('Table name:' + table.name)
    item = {
        'id': str(uuid.uuid1()),
        'text': text,
        'checked': False,
        'createdAt': timestamp,
        'updatedAt': timestamp,
    }
    try:
        # write the todo to the database
        table.put_item(Item=item)
        # create a response
        response = {
            "statusCode": 200,
            "body": json.dumps(item)
        }

    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        return response

Which is to be unit tested with this one:

import warnings
import unittest
import boto3
from moto import mock_dynamodb
import sys
import os
import json

@mock_dynamodb
class TestDatabaseFunctions(unittest.TestCase):
    def setUp(self):
        print ('---------------------')
        print ('Start: setUp')
        warnings.filterwarnings(
            "ignore",
            category=ResourceWarning,
            message="unclosed.*<socket.socket.*>")
        warnings.filterwarnings(
            "ignore",
            category=DeprecationWarning,
            message="callable is None.*")
        warnings.filterwarnings(
            "ignore",
            category=DeprecationWarning,
            message="Using or importing.*")
        """Create the mock database and table"""
        self.dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
        self.is_local = 'true'
        self.uuid = "123e4567-e89b-12d3-a456-426614174000"
        self.text = "Aprender DevOps y Cloud en la UNIR"

        from src.todoList import create_todo_table
        self.table = create_todo_table(self.dynamodb)
        #self.table_local = create_todo_table()
        print ('End: setUp')

    def test_put_todo(self):
        print ('---------------------')
        print ('Start: test_put_todo')
        # Testing file functions
        from src.todoList import put_item
        # Table local
        response = put_item(self.text, self.dynamodb)
        print ('Response put_item:' + str(response))
        self.assertEqual(200, response['statusCode'])
        # Table mock
        #self.assertEqual(200, put_item(self.text, self.dynamodb)[
        #                 'ResponseMetadata']['HTTPStatusCode'])
        print ('End: test_put_todo')

    def test_put_todo_error(self):
        print ('---------------------')
        print ('Start: test_put_todo_error')
        # Testing file functions
        from src.todoList import put_item
        # Table mock
        self.assertRaises(Exception, put_item("", self.dynamodb))
        self.assertRaises(Exception, put_item("", self.dynamodb))
        print ('End: test_put_todo_error')

And I cannot for the life of me cover the following lines from the first script with the tests on the second one:

    except ClientError as e:
        print(e.response['Error']['Message'])

Thank you in advance for your wisdom.

I wasn't able to cover it as I am a neofite in this subject.

javier
  • 1

1 Answers1

0

The easiest way to trigger an error scenario would be to call put_item without creating the table first. That would result in a ResourceNotFoundException, which triggers the ClientError.

From your example, it looks like you can simply comment out create_todo_table to test this scenario.


To give some background information:

AWS will throw a ClientError for the following exceptions:

DynamoDB.Client.exceptions.ConditionalCheckFailedException
DynamoDB.Client.exceptions.ProvisionedThroughputExceededException
DynamoDB.Client.exceptions.ResourceNotFoundException
DynamoDB.Client.exceptions.ItemCollectionSizeLimitExceededException
DynamoDB.Client.exceptions.TransactionConflictException
DynamoDB.Client.exceptions.RequestLimitExceeded
DynamoDB.Client.exceptions.InternalServerError

Moto currently only supports the following scenarios:

DynamoDB.Client.exceptions.ConditionalCheckFailedException
DynamoDB.Client.exceptions.ResourceNotFoundException
DynamoDB.Client.exceptions.ItemCollectionSizeLimitExceededException

Or in other words, Moto will throw an exception if:

  • the put_item contains an invalid ConditionExpression
  • the provided table does not exist
  • the size of the item is too large (>5MB, out the top of my head?)
Bert Blommers
  • 1,788
  • 2
  • 13
  • 19
  • This is awesome information, thank you. My follow up query would be, wouldn't commenting that line mess with the rest of the tests which rely on having that table created? I also need to cover the "if not dynamodb:" branch, I'm guessing it would be covered by dynamodb variable not existing, can that be done? – javier Jun 01 '23 at 13:09
  • Other tests would fail, yes. You can either move the `create_todo_table` into the `test_put_todo`-method, or simply create a new class specifically to test errors, something like `class TestDatabaseFunctionErrors` - whichever is easier – Bert Blommers Jun 01 '23 at 20:53