I have a unit test like so below:
# utilities.py def get_side_effects(): def side_effect_func3(self): # Need the "self" to do some stuff at run time. return {"final":"some3"} def side_effect_func2(self): # Need the "self" to do some stuff at run time. return {"status":"some2"} def side_effect_func1(self): # Need the "self" to do some stuff at run time. return {"name":"some1"} return side_effect_func1, side_effect_func2, side_effect_func2 ################# # test_a.py def test_endtoend(): s1, s2, s3 = utilities.get_side_effects() m1 = mock.MagicMock() m1.side_effect = s1 m2 = mock.MagicMock() m2.side_effect = s2 m3 = mock.MagicMock() m3.side_effect = s3 with mock.patch("a.get_request", m3): with mock.patch("a.get_request", m2): with mock.patch("a.get_request", m1): foo = a() # Class to test result = foo.run()
As part of the foo.run()
code run, get_request
is called multiple times. I want to have a different side_effect function for each call of get_request
method, in this case it is side_effect_func1
, side_effect_func2
, side_effect_func3
. But what I’m noticing is that only m1
mock object is active, i.e only side_effect_func1
is invoked but not the other 2. How do I achieve this?
I have also tried the below, but the actual side_effect functions don’t get invoked, they always return the function object
, but don’t actually execute the side_effect functions.
# utilities.py def get_side_effects(): def side_effect_func3(self): # Need the "self" to do some stuff at run time. return {"final":"some3"} def side_effect_func2(self): # Need the "self" to do some stuff at run time. return {"status":"some2"} def side_effect_func1(self): # Need the "self" to do some stuff at run time. return {"name":"some1"} all_get_side_effects = [] all_get_side_effects.append(side_effect_func1) all_get_side_effects.append(side_effect_func2) all_get_side_effects.append(side_effect_func3) return all_get_side_effects ######################### # test_a.py def test_endtoend(): all_side_effects = utilities.get_side_effects() m = mock.MagicMock() m.side_effect = all_side_effects with mock.patch("a.get_request", m): foo = a() # Class to test result = foo.run()
Advertisement
Answer
Your first attempt doesn’t work because each mock just replaced the previous one (the outer two mocks don’t do anything).
Your second attempt doesn’t work because side-effect is overloaded to serve a different purpose for iterables (docs):
If
side_effect
is an iterable then each call to the mock will return the next value from the iterable.
Instead you could use a callable class for the side-effect, which is maintaining some state about which underlying function to actually call, consecutively.
Basic example with two functions:
>>> class SideEffect: ... def __init__(self, *fns): ... self.fs = iter(fns) ... def __call__(self, *args, **kwargs): ... f = next(self.fs) ... return f(*args, **kwargs) ... >>> def sf1(): ... print("called sf1") ... return 1 ... >>> def sf2(): ... print("called sf2") ... return 2 ... >>> def foo(): ... print("called actual func") ... return "f" ... >>> with mock.patch("__main__.foo", side_effect=SideEffect(sf1, sf2)) as m: ... first = foo() ... second = foo() ... called sf1 called sf2 >>> assert first == 1 >>> assert second == 2 >>> assert m.call_count == 2