-1

I am pretty new to AsyncIO, and looking for a bit of help. I have a basic class that runs an aggregation pipeline, which works perfectly in production:

class BasePipeline2:
    client: motor.AsyncIOMotorClient
    db: motor.AsyncIOMotorDatabase
    collection: motor.AsyncIOMotorCollection

    def __init__(self, uri: str, database: str, collection: str):
        self.client = get_client(uri)
        self.db = self.client[database]
        self.collection = self.db[collection]

    async def run(self):
        """Runs the pipeline and returns the results as a list"""
        result = await self.collection.aggregate({"$match": {"foo": "bar"}}).to_list(length=None)
        return result

I have introduced the following test:

@pytest.mark.asyncio
async def test_run2(self):
    results = [{"_id": 1, "name": "Alice", "age": 25}, {"_id": 2, "name": "Bob", "age": 30}]
    cursor_mock = mock.AsyncMock()
    cursor_mock.to_list = mock.AsyncMock(return_value=results)

    collection_mock = mock.MagicMock()
    collection_mock.aggregate = mock.AsyncMock(return_value=cursor_mock)

    pipeline = BasePipeline2("mongodb://localhost:27017/", "test_db", "test_collection")
    with mock.patch.object(pipeline, "collection", collection_mock):
        pipeline_result = await pipeline.run()

    assert pipeline_result == results

I am trying to mock the to_list method to return the test results. Yet no matter what I try I get an error:

AttributeError: 'coroutine' object has no attribute 'to_list'

I can not seem to find a solution, and I feel that the the issue is somewhere in the way I set up my mocks.

E_net4
  • 27,810
  • 13
  • 101
  • 139
garyj
  • 1,302
  • 2
  • 13
  • 22

1 Answers1

0

If anyone else comes across this: it was an issue with the way the mocks were set up. The correct test that worked for me:

@pytest.mark.asyncio
async def test_run2(self):
    results = [{"_id": 1, "name": "Alice", "age": 25}, {"_id": 2, "name": "Bob", "age": 30}]
    cursor_mock = mock.AsyncMock()
    cursor_mock.to_list = mock.AsyncMock(return_value=results)

    collection_mock = mock.MagicMock()
    collection_mock.aggregate = mock.AsyncMock(return_value=cursor_mock)

    pipeline = BasePipeline2("mongodb://localhost:27017/", "test_db", "test_collection")
    with mock.patch.object(pipeline.collection, "aggregate", return_value=cursor_mock) as aggregate_mock:
        pipeline_result = await pipeline.run()

    aggregate_mock.assert_called_once_with({"$match": {"foo": "bar"}})
    cursor_mock.to_list.assert_awaited()
    assert pipeline_result == results

Notice the patching with mock.patch.object(pipeline.collection, "aggregate", return_value=cursor_mock), which patches the aggregate method to return the mock cursor.

E_net4
  • 27,810
  • 13
  • 101
  • 139
garyj
  • 1,302
  • 2
  • 13
  • 22