Search code examples
pythonunit-testingpython-asynciopython-unittest

Why is my async Python unit test using mock not catching the assertion?


I have a simple Python script check_all_members.py that calls the Microsoft Graph API to check some Entra ID groups and their members.

"""Check if a group contains any external users."""
import asyncio

from msgraph import GraphServiceClient
from azure.identity import DefaultAzureCredential

GROUP_OBJECT_ID = "a69bc697-1c38-4c81-be00-b2632e04f477"

credential = DefaultAzureCredential()
client = GraphServiceClient(credential)


async def get_group_members():
    """Get all members of a group and check if there are any external users."""
    members = await client.groups.by_group_id(GROUP_OBJECT_ID).members.get()

    externals = [
        member
        for member in members.value
        if member.user_principal_name.lower().startswith("x")
    ]

    assert not externals, "Group contains external users"


asyncio.run(get_group_members())

I'm trying to write a unit test for this function and here is what I've got so far.

import unittest
from unittest.mock import patch, AsyncMock
from check_all_members import get_group_members


class TestGetGroupMembers(unittest.IsolatedAsyncioTestCase):
    @patch("check_all_members.client")
    async def test_get_group_members_no_externals(self, mock_client):
        mock_members = AsyncMock()
        mock_members.get.return_value = {
            "value": [
                {"id": "123", "user_principal_name": "[email protected]"},
                {"id": "456", "user_principal_name": "[email protected]"},
            ]
        }

        mock_client.groups.by_group_id.return_value = mock_members

        await get_group_members()

        mock_client.groups.by_group_id.assert_called_once_with(
            "a69bc297-1c88-4c89-be00-b2622e04f475"
        )

That seems to work and also fails the test when I change the last assertion. However it should also raise an error since one "user_principal_name" starts with an "x". Unfortunately it doesn't and I cannot figure out why :(

        with self.assertRaises(AssertionError):
            await get_group_members()

I'm getting the error message and it looks like my returned mock object isn't working properly.

AssertionError: AssertionError not raised

Do you have any ideas?


Solution

    • Your mock response structure doesn't match the expected structure returned by client.groups.by_group_id(GROUP_OBJECT_ID).members.get(). The get() method would typically return an AsyncMock object that resolves to a response object, not a dictionary directly. So you should mock the .get() method to return an AsyncMock which, when awaited, gives the expected dictionary structure.
    • The assertRaises method is correctly used, but the way the get_group_members() function is structured might not be raising the AssertionError as expected. It is important to ensure that the assert not externals statement inside get_group_members() correctly raises an AssertionError when the condition is met.
    import unittest
    from unittest.mock import patch, AsyncMock
    from check_all_members import get_group_members
    
    
    class TestGetGroupMembers(unittest.IsolatedAsyncioTestCase):
        @patch("check_all_members.client")
        async def test_get_group_members_no_externals(self, mock_client):
            # Mocking the response structure
            mock_members_response = AsyncMock()
            mock_members_response.return_value = {
                "value": [
                    {"id": "123", "user_principal_name": "[email protected]"},
                    {"id": "456", "user_principal_name": "[email protected]"},
                ]
            }
            mock_members = AsyncMock()
            mock_members.members.get = mock_members_response
    
            mock_client.groups.by_group_id.return_value = mock_members
    
            # Testing for AssertionError
            with self.assertRaises(AssertionError):
                await get_group_members()
    
            mock_client.groups.by_group_id.assert_called_once_with(
                "a69bc697-1c38-4c81-be00-b2632e04f477"
            )