[英]Pytest monkeypatch isn't working on imported function
Suppose there are two packages in a project: some_package
and another_package
.假设一个项目中有两个包:
some_package
和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/OI want to avoid.我想测试
another_package.function.call_bar
some_package.foo.bar
因为它有一些网络 I/OI 想要避免。
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
.令我惊讶的是,它输出
hello
而不是patched
。 I tried to debug this thing, putting an IPDB breakpoint in the test.我试图调试这个东西,在测试中放置了一个IPDB断点。 When I manually import
some_package.foo.bar
after the breakpoint and call bar()
I get patched
.当我手动导入
some_package.foo.bar
断点和呼叫后bar()
我得到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.如果我在项目根目录中调用 pytest 我的函数没有被修补,但是当我指定
tests/test_bar.py
作为参数时 - 它可以工作。
As far as I understand it has something to do with the from some_package.foo import bar
statement.据我了解,它与
from some_package.foo import bar
语句有关。 If it's being executed before monkeypatching is happening then patching fails.如果在发生monkeypatching 之前执行它,则修补失败。 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?为什么它在遇到断点后可以在 IPDB REPL 中工作?
While Ronny's answer works it forces you to change application code.虽然Ronny 的回答有效,但它迫使您更改应用程序代码。 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 .
这在 unittest 模块的文档中提到。
monkeypatch.setattr('another_package.bar', lambda: print('patched'))
Named importation creates a new name for the object.命名导入为对象创建一个新名称。 If you then replace the old name for the object the new name is unaffected.
如果随后替换对象的旧名称,则新名称不受影响。
Import the module and use module.bar
instead.导入模块并使用
module.bar
代替。 That will always use the current object.那将始终使用当前对象。
EDIT:编辑:
import module
def func_under_test():
module.foo()
def test_func():
monkeypatch.setattr(...)
func_under_test
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应用程序/处理程序/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
check_user
第一直觉,像这样:
monkeypatch.setattr('auth.service.check_user', lambda x: return None)
But what you want to do is patch the instance in tasks.py
.但是您要做的是修补
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.虽然给出的答案已经很好,但我希望这能带来更完整的背景。
OP问题的正确答案:
monkeypatch.setattr('another_package.function.bar', lambda: print('patched'))
Another possible reason your function may not be getting patched is if your code is using multiprocessing.您的函数可能未被修补的另一个可能原因是您的代码是否使用了多处理。
On macOS the default start method for a new process has changed from fork
to spawn
.在 macOS 上,新进程的默认启动方法已从
fork
更改为spawn
。 If spawn
is used, a brand new Python interpreter process is started, ignoring your recently patched function.如果使用
spawn
,则会启动一个全新的 Python 解释器进程,忽略您最近修补的函数。
Fix: Set the default start method to fork
.修复:将默认启动方法设置为
fork
。
import multiprocessing
multiprocessing.set_start_method('fork', force=True)
You can add this snippet to conftest.py
inside your tests/
folder.您可以将此代码段添加到
tests/
文件夹中的conftest.py
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.