简体   繁体   English

当调用python Mock时,如何运行函数(以获得副作用)?

[英]How can I run a function (to get side effects) when a python Mock is called?

I am mocking (using python Mock) a function where I want to return a list of values, but in some of the items in the list I want a side effect to also occur (at the point where the mocked function is called). 我正在嘲笑(使用python Mock)一个函数,我想返回一个值列表,但是在列表中的一些项目中我也希望产生副作用(在调用模拟函数的位置)。 How is this most easily done? 这最容易做到的? I'm trying something like this: 我正在尝试这样的事情:

import mock
import socket

def oddConnect():
  result = mock.MagicMock()  # this is where the return value would go
  raise socket.error  # I want it assigned but also this raised

socket.create_connection = mock(spec=socket.create_connection,
  side_effect=[oddConnect, oddConnect, mock.MagicMock(),])
# what I want: call my function twice, and on the third time return normally
# what I get: two function objects returned and then the normal return

for _ in xrange(3):
  result = None
  try:
    # this is the context in which I want the oddConnect function call
    # to be called (not above when creating the list)
    result = socket.create_connection()
  except socket.error:
    if result is not None:
      # I should get here twice
      result.close()
      result = None
  if result is not None:
    # happy days we have a connection
    # I should get here the third time
    pass

The except clause (and it's internal if) I copied from the internals of socket and want to verify that I "test" that path through my copy of the code. 我从socket的内部复制了except子句(并且它是内部的),并且想要验证我通过我的代码副本“测试”该路径。 (I don't understand how socket can get to that code (setting the target while still raising an exception, but that isn't my concern, only the I verify that I can replicate that code path.) That's why I want the side effect to happen when the mock is called and not when I build the list. (我不明白套接字如何获得该代码(设置目标同时仍然引发异常,但这不是我的关注,只有我验证我可以复制该代码路径。)这就是为什么我想要这边调用mock时发生的效果,而不是我构建列表时发生的效果。

According to the unittest.mock documentation for side_effect : 根据side_effectunittest.mock文档

If you pass in an iterable, it is used to retrieve an iterator which must yield a value on every call. 如果传入一个iterable,它将用于检索迭代器,该迭代器必须在每次调用时产生一个值。 This value can either be an exception instance to be raised, or a value to be returned from the call to the mock ( DEFAULT handling is identical to the function case). 此值可以是要引发的异常实例,也可以是从对mock的调用返回的值( DEFAULT处理与函数大小写相同)。

Therefore, your socket.create_connection mock will return the function oddConnect for the first two calls, then return the Mock object for the last call. 因此, socket.create_connection mock将返回前两个调用的函数 oddConnect ,然后返回最后一次调用的Mock对象。 From what I understand, you want to mock create_connection object to actually call those functions as side effects rather than returning them. 根据我的理解,你想模拟create_connection对象来实际调用这些函数作为副作用而不是返回它们。

I find this behavior rather odd, since you'd expect side_effect , to mean side_effect in every case, not return_value . 我觉得这种行为很奇怪的,因为你所期望的side_effect ,意味着side_effect在任何情况下, return_value I suppose the reason this is so lies in the fact that the value of the return_value property must be interpreted as-is. 我认为原因在于return_value属性的值必须解释为return_value For instance, if your Mock had return_value=[1, 2, 3] , would your Mock return [1, 2, 3] for every call, or would it return 1 for the first call? 例如,如果你的Mock有return_value=[1, 2, 3] ,你的Mock会为每次通话返回[1, 2, 3] ,还是第一次通话时会返回1

Solution

Fortunately, there is a solution to this problem. 幸运的是,这个问题有一个解决方案。 According to the docs, if you pass a single function to side_effect , then that function will be called (not returned) every time the mock is called. 根据文档,如果你将一个函数传递给side_effect ,那么每次调用mock 都会调用 (不返回)该函数。

If you pass in a function it will be called with same arguments as the mock and unless the function returns the DEFAULT singleton the call to the mock will then return whatever the function returns. 如果传入一个函数,它将使用与mock相同的参数进行调用,除非函数返回DEFAULT单例,否则对mock的调用将返回函数返回的任何内容。 If the function returns DEFAULT then the mock will return its normal value (from the return_value ). 如果函数返回DEFAULT那么mock将返回其正常值(来自return_value )。

Therefore, in order to achieve the desired effect, your side_effect function must do something different every time it is called. 因此,为了达到预期的效果, side_effect函数必须在每次调用时执行不同的操作。 You can easily achieve this with a counter and some conditional logic in your function. 您可以使用计数器和函数中的某些条件逻辑轻松实现此目的。 Note that in order for this to work, your counter must exist outside the scope of the function, so the counter isn't reset when the function exits. 请注意,为了使其工作,您的计数器必须存在于函数范围之外,因此在函数退出时不会重置计数器。

import mock
import socket

# You may wish to encapsulate times_called and oddConnect in a class
times_called = 0
def oddConnect():
  times_called += 1
  # We only do something special the first two times oddConnect is called
  if times_called <= 2:
    result = mock.MagicMock()  # this is where the return value would go
    raise socket.error  # I want it assigned but also this raised  

socket.create_connection = mock(spec=socket.create_connection,
  side_effect=oddConnect)
# what I want: call my function twice, and on the third time return normally
# what I get: two function objects returned and then the normal return

for _ in xrange(3):
  result = None
  try:
    # this is the context in which I want the oddConnect function call
    # to be called (not above when creating the list)
    result = socket.create_connection()
  except socket.error:
    if result is not None:
      # I should get here twice
      result.close()
      result = None
  if result is not None:
    # happy days we have a connection
    # I should get here the third time
    pass

I also encountered the problem of wanting a side effect to occur for only some items in a list of values. 我还遇到了想要仅对值列表中的某些项发生副作用的问题。

In my case, I wanted to call a method from freezegun the third time my mocked method was called. 在我的情况下,我想在第三次调用我的freezegun方法时从freezegun调用一个方法。 These answers were really helpful for me; 这些答案对我很有帮助; I ended up writing up a fairly general wrapper class, which I thought I'd share here: 我最后写了一个相当普遍的包装类,我以为我会在这里分享:

class DelayedSideEffect:
    """
    If DelayedSideEffect.side_effect is assigned to a mock.side_effect, allows you to
    delay the first call of callback until after a certain number of iterations.
    """
    def __init__(self, callback, delay_until_call_num: int, return_value=DEFAULT):
        self.times_called = 0
        self.delay_until_call_num = delay_until_call_num
        self.callback = callback
        self.return_value = return_value

    def side_effect(self, *args, **kwargs):
        self.times_called += 1
        if self.times_called >= self.delay_until_call_num:
            self.callback()
        return self.return_value

To then return "my_default_return_value" without calling the lambda function on the first three calls: 然后返回“my_default_return_value”而不调用前三次调用的lambda函数:

with freeze_time(datetime.now()) as freezer:
    se = DelayedSideEffect(callback=lambda: freezer.move_to(the_future), 3)
    my_mock = MagicMock(return_value="my_default_return_value", side_effect=se)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我如何使用 Python MonkeyPatch + Mock 断言调用了 function - How can I use Python MonkeyPatch + Mock to assert that a function was called python unittest - 如何保留已转换为 Mock object 的函数的副作用? - python unittest - How to preserve a function's side effects which has been converted into a Mock object? Pytest 如何模拟依赖于其父调用参数的函数调用的副作用 - Pytest how do I mock side-effects for a function call that depends on its parent call parameter 如何使用unittest.mock从代码中删除副作用? - How can I use unittest.mock to remove side effects from code? 如何在 Python 的请求库中模拟会话中的副作用? - How to mock side effects in session in Python's request library? 如何在 Python 中运行具有固定效果的 diff-in-diff? - How can I run a diff-in-diff with fixed effects in Python? 如何模拟从字典中调用的函数? - How can I mock a function called from a dictionary? 如何模拟未按名称调用的 function? - How can I mock a function that isn't called by name? 如何模拟任何未被直接调用的 function? - How can I mock any function which is not being called directly? Python - 我如何断言模拟 object 没有被特定的 arguments 调用? - Python - How can I assert a mock object was not called with specific arguments?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM