0

I'm trying to use pytest-mock for mocking. This library is essentially a plugin/wrapper for mock and patch.

My problem is defined as:

I have an application (mymodule.py) that uses SQL Alchemy. Basically, there's a function that defines some objects from SQL Alchemy and returns a dictionary with those objects.

def some_function1():
    # some code
    from sqlalchemy import create_engine, MetaData, Table

    engine = create_engine(f"mysql+pymysql://{username}:{password}@{host}:{port})
    meta = MetaData(engine)
    my_table = Table(
        'my_table',
        meta,
        autoload=True,
        schema="some_schema"
    )
    db_tools = {"engine": engine, "table": my_table}
    return db_tools

Then, a second function takes that output dictionary as input and uses them:

def some_function2(db_tools, data):

    sql_query = db_tools["table"].insert().values(data)
    db_tools["engine"].execute(sql_query)
    # some more code

So now I'm writing unit tests, and I don't want to actually communicate with the real database. So I just need to mock everything sqlalchemy related. So far, I've managed to mock create_engine, MetaData and Table by doing:

mocker.patch(
    'my_module.create_engine',
    return_value=True
)
mocker.patch(
   'my_module.MetaData',
   return_value=True
)
mocker.patch(
   'my_module.Table',
   return_value=True
)

That allows me to test some_function1. But now I need to test some_function2, which uses the methods or attributes .insert(), .values and .execute(). How can I patch that?

4

1 回答 1

2

There is not much benefit to mocking some_function1 as it does nothing but establish a connection to the database. It doesn't take any input and all it returns is a dictionary pointing at a table and a connection. With respect to some_function2 we can just pass in multiple MagicMock's inside the db_tools argument and use configure_mock.

def test_some_function2(mocker):
    mock_table = mocker.MagicMock()
    mock_engine = mocker.MagicMock()

    fake_query = "INSERT blah INTO foo;"
    fake_data = [2, 3]

    mock_table.configure_mock(
        **{
            "insert.return_value": mock_table,
            "values.return_value": fake_query
        }
    )

    db_tools = {
        "table": mock_table,
        "engine": mock_engine
    }

    some_function2(db_tools, fake_data)

    mock_table.insert.assert_called_once()
    mock_table.values.assert_called_once_with(fake_data)

    mock_engine.execute.assert_called_once_with(fake_query)

When the test is run it returns the following.

========================================================== test session starts ==========================================================
platform darwin -- Python 3.7.4, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: ***
plugins: mock-3.2.0
collected 1 item                                                                                                                        

test_foo.py .                                                                                                                     [100%]

=========================================================== 1 passed in 0.01s ===========================================================
于 2021-05-21T18:19:02.897 回答