328

I am using pythons mock.patch and would like to change the return value for each call. Here is the caveat: the function being patched has no inputs, so I can not change the return value based on the input.

Here is my code for reference.

def get_boolean_response():
    response = io.prompt('y/n').lower()
    while response not in ('y', 'n', 'yes', 'no'):
        io.echo('Not a valid input. Try again'])
        response = io.prompt('y/n').lower()

    return response in ('y', 'yes')

My Test code:

@mock.patch('io')
def test_get_boolean_response(self, mock_io):
    #setup
    mock_io.prompt.return_value = ['x','y']
    result = operations.get_boolean_response()

    #test
    self.assertTrue(result)
    self.assertEqual(mock_io.prompt.call_count, 2)

io.prompt is just a platform independent (python 2 and 3) version of "input". So ultimately I am trying to mock out the users input. I have tried using a list for the return value, but that doesn't seam to work.

You can see that if the return value is something invalid, I will just get an infinite loop here. So I need a way to eventually change the return value, so that my test actually finishes.

(another possible way to answer this question could be to explain how I could mimic user input in a unit-test)


Not a dup of this question mainly because I do not have the ability to vary the inputs.

One of the comments of the Answer on this question is along the same lines, but no answer/comment has been provided.

Community
  • 1
  • 1
Nick Humrich
  • 14,905
  • 8
  • 62
  • 85
  • 5
    `response is not 'y' or 'n' or 'yes' or 'no'` in **not** doing what you think it does. See [How do I test one variable against multiple values?](http://stackoverflow.com/q/15112125) and you should *not* use `is` to compare string values, use `==` to compare *values*, not object identities. – Martijn Pieters Jul 22 '14 at 20:32
  • Also be careful here. It seems that you're trying to use `is` to compare string literals. Don't do that. The fact that it works (sometimes) is only an implementation detail in CPython. Also, `response is not 'y' or 'n' or 'yes' or 'no'` probably isn't doing what you think it is... – mgilson Jul 22 '14 at 20:32
  • 2
    You can use `@patch('foo.bar', side_effect=['ret1', ret2', 'ret3'])`. – Asclepius Oct 08 '20 at 18:22

4 Answers4

551

You can assign an iterable to side_effect, and the mock will return the next value in the sequence each time it is called:

>>> from unittest.mock import Mock
>>> m = Mock()
>>> m.side_effect = ['foo', 'bar', 'baz']
>>> m()
'foo'
>>> m()
'bar'
>>> m()
'baz'

Quoting the Mock() documentation:

If side_effect is an iterable then each call to the mock will return the next value from the iterable.

Nick Humrich
  • 14,905
  • 8
  • 62
  • 85
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Is there a way to do this with the standard `mock`? Is there a way to use patch with MagicMock like i am doing with standard mock? – Nick Humrich Jul 22 '14 at 20:39
  • 4
    @Humdinger: This is a feature of the stardard `Mock` class. – Martijn Pieters Jul 22 '14 at 20:39
  • @Humdinger: note however that `mock.patch()` defaults to using `MagicMock`, a subclass of `Mock`. – Martijn Pieters Jul 22 '14 at 20:41
  • 20
    Assigning a list appears to work with python 3 only. Testing with python 2.7 I need to use an iterator instead (`m.side_effect = iter(['foo', 'bar', 'baz'])`). – user686249 Aug 12 '15 at 12:54
  • @user686249: interesting, because a list *is* an iterable. – Martijn Pieters Aug 12 '15 at 12:55
  • @user686249: actually, I cannot reproduce that. Not with Python 2.7 and not with Python 3. The `Mock.side_effect` property wraps an iterator in an iterable. How did you set the side effect, exactly? You did create an *instance* of `Mock` or `MagicMock`, right? – Martijn Pieters Aug 13 '15 at 17:34
  • @MartijnPieters: My assignment looks like `self.MockSession.post.side_effect = testResponses` (`testResponses` being the list), with `self.MockSession.post` being created via `mock.create_autospec(requests.Session().post)`, so it should be a `MagicMock`. The issue might be that I use the python-mock package that comes with openSUSE 13.2, which is not really up to date (version 1.0.1-11.1.4). – user686249 Aug 14 '15 at 09:24
  • @user686249: I'll take a look if I have time. Note that creating a spec with `create_autospec()` from a bound method is.. rather pointless. Better spec from `requests.Response` and set that as a return value (or a list of values) on a `MagicMock()` object that patches the `post` method. – Martijn Pieters Aug 14 '15 at 10:00
  • 1
    @user686249: I can indeed reproduce this, because speccing from a method produces a `lambda` (a function), not a `MagicMock`. A function object cannot *have* properties, so the `side_effect` attribute *has* to be an iterable. You should not be speccing the method like that though. Better use [`mock.patch.object(requests.Session, 'post')`](https://docs.python.org/3/library/unittest.mock.html#patch-object); that results in a patcher object that properly auto-specs on the method, **and** supports `side_effect` properly. – Martijn Pieters Aug 14 '15 at 10:27
  • @MartijnPieters: Thanks for the research. Reviewing my code I noticed what I do is overly complicated anyway. My `self.MockSession` is a mocked autospec'ed `Session` object anyway, so patching its methods manually is entirely superfluous and assigning a list to `side_effect` works as advertised. Sorry for the noise (though I guess at least I learned something :-)). – user686249 Aug 14 '15 at 12:51
  • @MartijnPieters How do I use this with `mock.patch`? e.g. `mock_arg.side_effect = [1, 2]` doesn't work. – Hussain Jun 07 '18 at 05:13
  • @Hussain: `mock.patch()` is not special. The code in the question here uses `mock.patch()`. I can't help you with this little information; make sure you patched something that is actually called and is not a method object. – Martijn Pieters Jun 07 '18 at 12:01
  • What happens if it gets called more times than the number of items in the iterable? – JoeMjr2 Feb 22 '19 at 22:24
  • What if I want it to return Foo the first time it's called, and then return Bar every other time it's called thereafter? – JoeMjr2 Feb 22 '19 at 22:26
  • 6
    @JoeMjr2: When the iterator is exhausted, `StopIteration` is raised. You can use any iterator, so you could use `itertools.chain(['Foo'], itertools.repeat('Bar'))` to produce `Foo` once, then forever produce `Bar`. – Martijn Pieters Feb 23 '19 at 15:38
  • just to abridge the solution, you can also do: `m = Mock(side_effect=['foo', 'bar', 'baz'])` Or maybe: `m = Mock(side_effect=iter(['foo', 'bar', 'baz']))` as @user686249 recommended. – nightfury May 24 '21 at 07:33
  • @NickHumrich yes, `obj.read_data = MagicMock(side_effect=[1, 2, 3, 4])` – Elephant Jan 27 '23 at 16:23
15

for multiple return values, we can use side_effect during patch initializing also and pass iterable to it

sample.py

def hello_world():
    pass

test_sample.py

from unittest.mock import patch
from sample import hello_world

@patch('sample.hello_world', side_effect=[{'a': 1, 'b': 2}, {'a': 4, 'b': 5}])
def test_first_test(self, hello_world_patched):
    assert hello_world() == {'a': 1, 'b': 2}
    assert hello_world() == {'a': 4, 'b': 5}
    assert hello_world_patched.call_count == 2
shrhawk
  • 621
  • 7
  • 10
0

I think the simplest solution is using a combination of iter(), patch(), side_effect.

from unittest.mock import patch
from return_inputs import *

def test_something_that_has_2_inputs(self):
        input_list = ['Foo', 'Bar']
        inputs = iter(input_list)
        with patch("builtins.input", side_effect=inputs):
            name1, name2 = return_inputs()
        assert name1 == "Foo"
        assert name2 == 'Bar'
Ambassador Kosh
  • 459
  • 5
  • 19
-9

You can also use patch for multiple return values:

@patch('Function_to_be_patched', return_value=['a', 'b', 'c'])

Remember that if you are making use of more than one patch for a method then the order of it will look like this:

@patch('a')
@patch('b')
def test(mock_b, mock_a);
    pass

as you can see here, it will be reverted. First mentioned patch will be in the last position.

  • 3
    This doesn't work. It just returns the entire list `['a', 'b', 'c']` each time you call the patched function. – Cerin May 15 '21 at 03:44