Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

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.

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.

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.

Become a Member to join the conversation.