0

I have a test module, test_roles.py which is trying to test the module roles.py. I want to mock out the use of MongoClient from pymongo within the roles module, so that my unit tests do not rely on an external service. The following is a simplified example of what I'm doing that isn't working for me. How do I get this to work so that I can fake out MongoClient everywhere?

In roles.py:

from pymongo import MongoClient
client = MongoClient(...)

In test_roles.py:

import roles
def test_mock():
    assert type(roles.client).__name__ == 'FakeMongoClient'

In conftest.py:

import pytest
import pymongo

@pytest.fixture(autouse=True)
def fake_mongo(monkeypatch):
    class FakeMongoClient():
         pass
    monkeypatch.setattr(pymongo, 'MongoClient', FakeMongoClient)

I don't believe the question identified as a duplicate is asking the same thing. Editing a module's global variable after-the-fact is different from modifying the dependency such that the actions that occur during import use the mocked dependency. In this example, unless the MongoClient initialization uses lazy connections, failing to mock before first import means we get a failure during import of roles.

user108471
  • 2,488
  • 3
  • 28
  • 41
  • Possible duplicate of [Modifying global variables in Python unittest framework](https://stackoverflow.com/questions/6268278/modifying-global-variables-in-python-unittest-framework) – quamrana May 03 '18 at 19:43
  • That question seems like it's asking about a different approach than the one I'm asking about. I'm asking about patching a dependency module before my globals even get set. That's about manipulating the globals directly. – user108471 May 03 '18 at 19:48
  • Could you change your `test_roles.py` to perform the `import roles` inside `test_mock()`? – quamrana May 03 '18 at 20:06
  • @quamrana It never occurred to me as a reasonable thing to try. It didn't work, however. It looks like the problem might be that monkeypatch is function scoped, so the first execution of the fixture doesn't take place until after the import in the test module? Not sure how to fix that. – user108471 May 03 '18 at 20:13
  • So maybe you could change`roles.py` to not have a global, but have a function which lazily initializes an instance on the function. – quamrana May 03 '18 at 20:19
  • @quamrana That may be a good workaround if there's no way to mock it this way. I just dislike the trick of hanging instances directly onto a function object, so I try to avoid it. – user108471 May 03 '18 at 20:44

1 Answers1

3

roles.py uses roles.MongoClient, not pymongo.MongoClient, to define roles.client, due to how you imported the name. You need to patch the same:

monkeypatch.setattr(roles, 'MongoClient', FakeMongoClient)

Your original patch should work if roles.py looked like

import pymongo
client = pymongo.MongoClient()
chepner
  • 497,756
  • 71
  • 530
  • 681
  • This does not work either. It seems to patch out MongoClient *after* the globals get defined, so client is still an instance of the original MongoClient, but roles.pymongo.MongoClient is now FakeMongoClient. – user108471 May 03 '18 at 19:38
  • Hm. `roles.pymongo` shouldn't even exist, as you never imported the name `pymongo` into your `roles` module. – chepner May 03 '18 at 19:40
  • Sorry for lack of clarity. I tried to change it in both ways you suggested, monkeypatching roles didn't work, and changing roles.py to import pymongo as in your second example. I was talking about the second strategy. – user108471 May 03 '18 at 19:43
  • OK, yeah, I don't know enough about `pytest` specifically to know exactly when `fake_mongo` gets defined and used relative to when `roles` is imported. – chepner May 03 '18 at 19:45