Stupid.Fat.Cat
Stupid.Fat.Cat

Reputation: 11325

How can I mock an object's function call in another function that doesn't necessarily exist because of moto's mock

I have a function called check_stuff and it instantiates an object and calls the function describe_continuous_backups, however, moto doesn't have support for this yet so I need to manually mock it myself. I have the following but it doesn't seem like I'm able to patch the object. How can I go about this?

def check_stuff(profile, table_name):
    session = boto3.Session(profile_name=profile)
    client = session.client('dynamodb', 'eu-west-1')
    some_stuff = client.describe_continuous_backups(TableName=table_name)
    #dostuff

@mock_dynamodb2
@mock.patch('boto3.Session.client.describe_continuous_backups', return_value={'foo': 'bar'})
def test_continuous_backup_disabled(self):
    table = self.client.create_table(
        TableName='Movies',
        KeySchema=[
            {
                'AttributeName': 'year',
                'KeyType': 'HASH'
            },
            {
                'AttributeName': 'title',
                'KeyType': 'RANGE'
            }
        ],
        AttributeDefinitions=[
            {
                'AttributeName': 'year',
                'AttributeType': 'N'
            },
            {
                'AttributeName': 'title',
                'AttributeType': 'S'
            },

        ],
        ProvisionedThroughput={
            'ReadCapacityUnits': 10,
            'WriteCapacityUnits': 10
        }
    )
    result = check_stuff('myprofile', 'some_table')

I can try and mock the client like so:

mocker.patch('mypackage.boto3.Session.client', ....)

But the problem with that is that it mocks the client itself. I need to mock a function that doesn't necessarily exist while retaining the rest of the functionality.

Upvotes: 2

Views: 1036

Answers (1)

blhsing
blhsing

Reputation: 107085

boto3.client returns an instance of a dynamically-created class based on the service_name argument (see source), so you cannot use the patch method, which requires that the target object be importable.

Instead, you can patch botocore.client.ClientCreator._create_methods, the method that dynamically creates methods for the class that boto3.client returns, with a wrapper function that makes the describe_continuous_backups attribute a Mock object with the given return_value:

import boto3
import botocore
from unittest.mock import patch, Mock

def override(*args, **kwargs):
    def wrapper(self, service_model):
        op_dict = original_create_methods(self, service_model)
        if 'describe_continuous_backups' in op_dict:
            op_dict['describe_continuous_backups'] = Mock(*args, **kwargs)
        return op_dict
    return wrapper
original_create_methods = botocore.client.ClientCreator._create_methods

@patch('botocore.client.ClientCreator._create_methods', override(return_value={'foo': 'bar'}))
def check_stuff():
    session = boto3.Session()
    client = session.client('dynamodb', 'eu-west-1')
    some_stuff = client.describe_continuous_backups(TableName='')
    return some_stuff

print(check_stuff())

This outputs:

{'foo': 'bar'}

Upvotes: 2

Related Questions