76

I want to be able to have multiple calls to a particular attribute function return a different result for each successive call.

In the below example, I would like increment to return 5 on its first call and then 10 on its second call.

Ex:

import mock

class A:
    def __init__(self):
        self.size = 0
    def increment(self, amount):
        self.size += amount
        return amount

@mock.patch("A.increment")
def test_method(self, mock_increment):
    def diff_inc(*args):
        def next_inc(*args):
            #I don't know what belongs in __some_obj__
            some_obj.side_effect = next_inc
            return 10
        return 5

    mock_increment.side_effect = diff_inc

The below page has almost everything that I need except that it assumes that the caller would be an object named "mock", but this can't be assumed.

http://mock.readthedocs.org/en/latest/examples.html#multiple-calls-with-different-effects

FearlessFuture
  • 2,250
  • 4
  • 19
  • 25

4 Answers4

122

You can just pass an iterable to side effect and have it iterate through the list of values for each call you make.

@mock.patch("A.increment")
def test_method(self, mock_increment):
    mock_increment.side_effect = [5,10]
    self.assertEqual(mock_increment(), 5)
    self.assertEqual(mock_increment(), 10)
Silfheed
  • 11,585
  • 11
  • 55
  • 67
  • 9
    Also, this seems to work: `@mock.patch("A.increment", side_effect=[5, 10])` – santon Jun 11 '21 at 20:48
  • NB: This does not work directly for callables. For a function f, `mock.side_effect = f`, `m() == f()`; but if `mock.side_effect = [f, f, f]`, then `m() == f` (the function object, rather than the function actually being called). – gerrit Jun 28 '23 at 10:35
11

I tested and this should work

import mock

...
...

@mock.patch.object(ClassB, 'method_2')
@mock.patch.object(ClassA, 'method_1')
def test_same_method_multi_return_value(self, method_1, method_2):
    # type: () -> None

    method_1.return_value = 'Static value'
    method_1.side_effect = [
        'Value called by first time'
        'Value called by second time'
        '...'
    ]

Version

https://mock.readthedocs.io/en/latest/
mock>=2.0.0,<3.0
Binh Ho
  • 3,690
  • 1
  • 31
  • 31
5

I think the popping values off of a list method will be more straightforward. The below example works for the test you wanted to perform.

Also, I've had a difficult time with the mock library before and have found that the mock.patch.object() method was typically easier to use.

import unittest
import mock


class A:
    def __init__(self):
        self.size = 0

    def increment(self, amount):
        self.size += amount
        return amount

incr_return_values = [5, 10]


def square_func(*args):
    return incr_return_values.pop(0)


class TestMock(unittest.TestCase):

    @mock.patch.object(A, 'increment')
    def test_mock(self, A):
        A.increment.side_effect = square_func

        self.assertEqual(A.increment(1), 5)
        self.assertEqual(A.increment(-20), 10)
Nate Brennand
  • 1,488
  • 1
  • 12
  • 20
0

You can use patch and set the absolute path to the module.

from unittest.mock import patch

@patch("src.module2.requests.post")
@patch("src.module1.requests.get")
def test_mock(self, mock_get, mock_post):
   data = {}
   mock_post.return_value.status_code = 200
   mock_post.return_value.json.return_value = data
   mock_get.return_value.json.return_value = data

The order used in patches must be kept in method mock parameters, module1 refers to mock_get and module2 refers to mock_post.

Angelo Mendes
  • 905
  • 13
  • 24