barryodev
barryodev

Reputation: 534

How can I use pytest monkeypatch to mock calls to SQLAlchemy create_engine call

I have a function that returns an SQLAlchemy engine object

create_db.py:

from sqlalchemy import create_engine


def init_db():
    return create_engine("sqlite://", echo=True, future=True)

and I've a test that's attempting to use pytest's monkeypatch to mock the call to create_engine.

test_db.py:

import sqlalchemy
from create_db import init_db


def test_correct_db_path_selected(monkeypatch):
    def mocked_create_engine():
        return "test_connection_string"

    monkeypatch.setattr(sqlalchemy, "create_engine", mocked_create_engine())
    engine = init_db()
    assert engine == "test_connection_string"

When I run pytest, the test is failing as a real sqlalchemy engine object is getting returned, not the mocked string.

AssertionError: assert Engine(sqlite://) == 'test_connection_string'

I've tried the following calls to setattr but they all fail in the same manner:

    monkeypatch.setattr("sqlalchemy.engine.create.create_engine", mocked_create_engine)
    monkeypatch.setattr(sqlalchemy.engine.create, "create_engine", mocked_create_engine)
    monkeypatch.setattr(sqlalchemy.engine, "create_engine", mocked_create_engine)

I've gotten the basic examples from pytest docs to work but it doesn't cover a static function from a library. Does anyone have any suggestions on what I'm doing wrong?

Upvotes: 1

Views: 1740

Answers (1)

barryodev
barryodev

Reputation: 534

So I've found a solution for my problem, but I'm still not clear on why the above code doesn't work.

If I change my create_db.py to directly call sqlalchemy.create_engine, the mocking function works.

create_db.py:

import sqlalchemy


def init_db():
    return sqlalchemy.create_engine("sqlite://")

test_db.py:

import sqlalchemy
from create_db import init_db

class MockEngine:
    def __init__(self, path):
        self.path = path

def test_correct_db_path_selected(monkeypatch):
    def mocked_create_engine(path):
        return MockEngine(path=path)

    monkeypatch.setattr(sqlalchemy, "create_engine", mocked_create_engine)
    engine = init_db()
    assert engine.path == "sqlite://"

I don't mind changing my code to make it more testable but I'd still like to know if it's possible to mock a call to the original create_engine call. I'll leave the question and answer up in case any else runs into the same problem.

Edit: I found a solution that doesn't involve changing the code to be tested. The following call to setattr will mock a function call that isn't on an object:

monkeypatch.setattr(create_db, "create_engine", mocked_create_engine)

This works as it's telling monkeypatch to mock direct calls to create_engine in the create_db.py file.

Upvotes: 1

Related Questions