372

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.

4
  • 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? and you should not use is to compare string values, use == to compare values, not object identities. Commented Jul 22, 2014 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... Commented Jul 22, 2014 at 20:32
  • 2
    You can use @patch('foo.bar', side_effect=['ret1', ret2', 'ret3']). Commented Oct 8, 2020 at 18:22
  • 1
    It seem like a oversight of the library, that you can set a iterable for side_effects but not for return_value . It violates the principle of least surprise. Commented Jul 11, 2024 at 8:42

4 Answers 4

630

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.

Sign up to request clarification or add additional context in comments.

17 Comments

@Humdinger: This is a feature of the stardard Mock class.
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: 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'); that results in a patcher object that properly auto-specs on the method, and supports side_effect properly.
@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.
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.
|
24

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

Comments

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'

Comments

-10

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.

2 Comments

This doesn't work. It just returns the entire list ['a', 'b', 'c'] each time you call the patched function.
side_effect instead of return_value does that. doc

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.