Side Effects as an Iterable (Part 2)
00:00 Okay, so it appears that you may only be able to use the arguments and the return value of a function once. That’s kind of my hypothesis. So what I want to try is to change the order of the iterable.
00:16
So, the first time we call it, we’ll call this .log_request()
function, and then the second time will be the Timeout
, ha. It’s a little bit strange, but I just want to see if this works.
00:31
We’re going to first assert the response and then assert that it raises the Timeout
, and it should have still been called twice. Let’s clear the screen, run our tests again, and—that still didn’t work.
00:49
So I’m going to undo what I did, because that wasn’t the problem. The problem, I think, is that when you set .side_effect
as an iterable, it gets reset each time you call it. So when we call get_holidays()
, we’re calling requests.get()
, and maybe the .side_effect
just gets wiped out.
01:14
Let’s kind of verify this. Let’s do a print statement and we’ll print '1: '
, the first time is going to be requests.get.side_effect
.
01:32 And then after we call the function, the first time we’ll print out the side effects again, and then let’s save that and run our program again, see if it printed out anything. Okay, so that’s not really helpful. Um, hm.
01:55
So, this is a list_iterator
object—maybe we can just make this a list
. Let’s try to cast it as a list
.
02:11 Save that, try this again.
02:16
Um, okay. This is a little helpful. Right, so the first time we have a list of the Timeout
and then the .log_request()
method,
02:34
and then we get an error and we get a StopIteration
error. What that tells me is that the .side_effect
list—or rather, list_iterator
object—is exhausted before we hit the second time.
02:51
So the .side_effect
list here seems to be getting reset after the first call to requests.get()
via this get_holidays()
function.
03:03
So in order for this test to actually work, we would need to reassign the .side_effect
after the function call, and that would look something like,
03:17
we would have it as Timeout
first, and then we call to .log_request()
second, but that beats the purpose of using .side_effect
as an iterable.
03:30
So let’s see if there’s another way around this. Since we know that the .side_effect
iterable gets cleared every time we call requests.get()
, one way we can get around this is to
03:46
take the logic of our get_holidays()
function—we can take the logic of get_holidays()
and bring it into this test itself. If we were to go in here and take the response_mock
and bring it into our test function, once we have this response_mock
we could say the side effects are going to be Timeout
and then response_mock
.
04:16
So that way we’re only going to be calling requests.get()
once, and then the .side_effect
iterable, this list_iterator
, is not going to get reset. And let me re-fix the typo, and let’s go ahead and clear the screen and try this again.
04:36
We get an AssertionError
this time, that .call_count
equals 2
.
04:42
I’m guessing it’s because we called requests.get()
in another test.
04:50
Right, so here we’ve called it once and then twice, so it appears that the .call_count
is actually being accumulated through all of our tests.
05:02
Let’s verify that. I’m actually curious to see what the .call_count
is. Let’s print 'call count: '
, and this will be requests.get.call_count
.
05:17
I’m guessing it’s going to be 4
since we’ve called it four times in our test cases. Let’s clear the screen and see—yup. There it is, 4
. So, this is kind of annoying.
05:30
Maybe what we can do instead is at the beginning of our tests, we can say requests.get.call_count = 0
, and then that way we’re starting with a fresh .call_count
.
05:47
And what you would probably do in a more real-world situation is create a set up function within your tests to reset the .call_count
, if you wanted to do something like that.
06:00
So, yeah. Now this test is kind of more reflective of what is actually happening within the test—rather, the .call_count
is more reflective of that.
06:10
So now that we’ve reset it to 0
, we’ve called requests.get()
once here and once here, so that should be equal to 2
. Let’s clear the screen, try this again.
06:22
Boo-yeah, there we go. 3 tests
, successful. We’ve kind of learned—I’ve learned—about side effects and using them as an iterable is kind of tricky.
06:33
Each time you call the function that you have set the .side_effect
to, the iterable will get reset. So you either have to take the logic out of the function you’re calling and bring it into your tests, or you have to reset the iterable each time.
06:50 Thanks for bearing with me through this debugging. I hope this was helpful in your learning, as well as mine.
Chris James on May 17, 2020
I’m sorry but this lesson is a hot mess! There’s something to be said about watching experienced developers mess up and persist, it’s important to dispel the myth of the god like creator. (Watch me code Twitch streams for example) Not reading the manual carefully before recording a lesson… This course needs a redo, or at least this lesson.
alistairjames on July 5, 2020
The bonus lesson on how to explore your way out of a hole was great!
Sam Martin on Aug. 27, 2020
I’m inclined to agree with Chris. In a previous lesson you had a name clash with something else called calendar which may have been an issue that some-one following the tutorial would have had anyway if they didn’t follow your file naming exactly, so that made sense to leave in. But not properly testing the behaviour of your demo beforehand and leaving it in the final video isn’t good enough for a paid service, and is very much not in line with the quality of the videos elsewhere on this site which are excellent in this respect.
Charles on Sept. 23, 2020
Rather than the interator being exhausted, it appears that you can’t use a function in a side_effect
iterable.
If you pass in an iterable, it is used to retrieve an iterator which must yield a value on every call. 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).
docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect
Am I just reading the docs wrong?
adamgranthendry on March 30, 2021
@charles and @yanxin The author came to an erroneous conclusion, which misinstructs students who watch this.
You can actually put a function in the iterable, but side_effect
will return exactly that function. The arguments of the Mock
don’t get passed to it; that only happens when side_effect is passed just a function.
Had the author debugged and stepped through the code, he would have seen that side_effect
returns log_request
itself, which is why he recieved the error that 'function' object has no attribute 'status_code'
.
The iterator is not being reset. Calling list
on an iterable consumes all elements of the iterable to create the list, which is why he saw a StopIteration
error. To convince yourself, try this:
>>> a = iter([1,2])
>>> list(a)
[1, 2]
>>> list(a)
[]
>>> b = iter([1,2,3])
>>> next(b)
1
>>> list(b)
[2, 3]
>>> next(b)
StopIteration
His code could have been fixed simply by using self.log_request('http://localhost/api/holidays')
:
or by passing a Mock
object, which he did when he moved response_mock
into the test.
Either way, the error isn’t because the iterator is reset with each call. It is because he passed a function and tried to call an attribute status_code
from it.
Vladislav S on May 4, 2021
This lesson isn’t even about troubleshooting a problem, it’s 6 minutes of guessing till it worked. Not very useful.
torrepreciado on Dec. 7, 2021
Could this lesson be recorded back again with an actual troubleshoot of the problem and not just random guesses?
artemudovyk on April 13, 2022
This topic is hard enough without this kind of guesses in a form of a “Tutorial”. Rerecord it, please. It shouldn’t be here in the first place.
R on May 17, 2022
This “tutorial” is a mess, which only confuses people.
Become a Member to join the conversation.
Yanxin Wang on May 15, 2020
According to docs on side_effect, when an iterable is passed in to
side_effect
, it must yield either a value to be returned from the call to the mock or an exception instance.