15

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?

1 Answer 1

13

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 ===========================================================
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.