Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python pytest mock fails with "assert None" for function call assertions

I am trying to mock some calls to boto3 and it looks like the mocked function is returning the correct value, and it look like if I change the assertion so it no longer matches what was passed in the assertion fails because the input parameters do not match, however if I make them match then the assertion fails with:

E       AssertionError: assert None
E        +  where None = <bound method wrap_assert_called_with of <MagicMock name='get_item' id='139668501580240'>>(TableName='TEST_TABLE', Key={'ServiceName': {'S': 'Site'}})
E        +    where <bound method wrap_assert_called_with of <MagicMock name='get_item' id='139668501580240'>> = <MagicMock name='get_item' id='139668501580240'>.assert_called_with
E        +      where <MagicMock name='get_item' id='139668501580240'> = <botocore.client.DynamoDB object at 0x7f071b8251f0>.get_item
E        +        where <botocore.client.DynamoDB object at 0x7f071b8251f0> = site_dao.ddb_client

The dynamo DB object is a global variable.

ddb_client = boto3.client("dynamodb")

def query_ddb():
        """Query dynamo DB for the current strategy.

        Returns:
            strategy (str): The latest strategy from dynamo DB.

        """
        response = None

        try:
            ddb_table = os.environ["DDB_DA_STRATEGY"]

            response = ddb_client.get_item(
                TableName=ddb_table, Key={"ServiceName": {"S": "Site"}}
            )
        except Exception as exception:
            LOGGER.error(exception)

            raise ServerException("The server was unable to process your request.", [])

        return response.get("Item").get("Strategy").get("S")

And my unit test looks like:

def test_data_access_context_strategy_ddb(mocker):
    object_key = {
        "ServiceName": {"S": "Site"}
    }

    table_name = "TEST_TABLE"

    expected_response = "SqlServer"

    os_env = {
        "DDB_DA_STRATEGY": table_name,
    }

    ddb_response = {
        "Item": {
            "Strategy": {
                "S": expected_response
            }
        }
    }

    mocker.patch.dict(os.environ, os_env)

    mocker.patch.object(site_dao.ddb_client, "get_item")

    site_dao.ddb_client.get_item.return_value = ddb_response

    data_access_context = site_dao.DataAccessContext()

    response = data_access_context.query_ddb()
    
    assert response == expected_response

    assert site_dao.ddb_client.get_item.assert_called_with(TableName=table_name, Key=object_key)

I cant work out what is going wrong, if I change the expected value for assert_called_with, so for example:

assert site_dao.ddb_client.get_item.assert_called_with(TableName="TT", Key=object_key)

The test fails with:

E       AssertionError: expected call not found.
E       Expected: get_item(TableName='TT', Key={'ServiceName': {'S': 'Site'}})
E       Actual: get_item(TableName='TEST_TABLE', Key={'ServiceName': {'S': 'Site'}})
E       
E       pytest introspection follows:
E       
E       Kwargs:
E       assert {'Key': {'Ser... 'TEST_TABLE'} == {'Key': {'Ser...leName': 'TT'}
E         Omitting 1 identical items, use -vv to show
E         Differing items:
E         {'TableName': 'TEST_TABLE'} != {'TableName': 'TT'}
E         Use -v to get the full diff

So when the expected and actual inputs differ then it fails because of this, however when they are the same and the test should pass it fails because it is as if the function were never even called.

like image 660
berimbolo Avatar asked Aug 05 '20 13:08

berimbolo


People also ask

How do I mock a function with pytest-mock?

Let’s mock this function with pytest-mock. Pytest-mock provides a fixture called mocker. It provides a nice interface on top of python's built-in mocking constructs. You use mocker by passing it as an argument to your test function, and calling the mock and patch functions from it.

How to use assertisnone () in Python?

assertIsNone () in Python is a unittest library function that is used in unit testing to check that input value is None or not. This function will take two parameters as input and return a boolean value depending upon assert condition. If input value is equal to None assertIsNone () will return true else return false.

Why does assert false return none?

That return value is None when the assertion passes. So then the whole line evaluates to assert None, and None is false-y, so you end up with assert False. tl;dr Don't use assert twice. Thanks I completely missed this and thought it was a configuration issue, not sure if your double reply was 'tongue-in-cheek'!

Why am I getting warnings from xfail tests about asserting none?

Running pytest master I see warnings from XFAIL tests about asserting None. The warnings suggests to change the assert to assert obj is None but the reason we are asserting None is because the test fails (hence XFAIL ).


1 Answers

You have two assertions on this line:

assert site_dao.ddb_client.get_item.assert_called_with(TableName=...)

The first assertion is assert_called_with which sounds like it is what you want. Then you have another assertion at the beginning of the line: assert ... which asserts on the return value of the assert_called_with function. That return value is None when the assertion passes. So then the whole line evaluates to assert None, and None is false-y, so you end up with assert False.

tl;dr Don't use assert twice.

like image 127
Oin Avatar answered Oct 17 '22 10:10

Oin