Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pytest monkeypatch isn't working on imported function

Suppose there are two packages in a project: some_package and another_package.

# some_package/foo.py: def bar():     print('hello') 
# another_package/function.py from some_package.foo import bar  def call_bar():     # ... code ...     bar()     # ... code ... 

I want to test another_package.function.call_bar mocking out some_package.foo.bar because it has some network I/O I want to avoid.

Here is a test:

# tests/test_bar.py from another_package.function import call_bar  def test_bar(monkeypatch):     monkeypatch.setattr('some_package.foo.bar', lambda: print('patched'))     call_bar()     assert True 

To my surprise it outputs hello instead of patched. I tried to debug this thing, putting an IPDB breakpoint in the test. When I manually import some_package.foo.bar after the breakpoint and call bar() I get patched.

On my real project the situation is even more interesting. If I invoke pytest in the project root my function isn't patched, but when I specify tests/test_bar.py as an argument - it works.

As far as I understand it has something to do with the from some_package.foo import bar statement. If it's being executed before monkeypatching is happening then patching fails. But on the condensed test setup from the example above patching does not work in both cases.

And why does it work in IPDB REPL after hitting a breakpoint?

like image 218
Glueon Avatar asked Jul 09 '15 00:07

Glueon


2 Answers

While Ronny's answer works it forces you to change application code. In general you should not do this for the sake of testing.

Instead you can explicitly patch the object in the second package. This is mentioned in the docs for the unittest module.

monkeypatch.setattr('another_package.bar', lambda: print('patched')) 
like image 88
Alex Avatar answered Sep 27 '22 17:09

Alex


As Alex said, you shouldn't rewrite your code for your tests. The gotcha I ran into is which path to patch.

Given the code:

app/handlers/tasks.py

from auth.service import check_user  def handle_tasks_create(request):   check_user(request.get('user_id'))   create_task(request.body)   return {'status': 'success'} 

Your first instinct to monkeypatch check_user, like this:

monkeypatch.setattr('auth.service.check_user', lambda x: return None) 

But what you want to do is patch the instance in tasks.py. Likely this is what you want:

monkeypatch.setattr('app.handlers.tasks.check_user', lambda x: return None) 

While the answers given are already good, I hope this brings more complete context.

like image 20
alairock Avatar answered Sep 27 '22 19:09

alairock