Locked learning resources

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

Unlock This Lesson

Locked learning resources

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

Unlock This Lesson

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.

Avatar image for Yanxin Wang

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.

Avatar image for Chris James

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.

Avatar image for alistairjames

alistairjames on July 5, 2020

The bonus lesson on how to explore your way out of a hole was great!

Avatar image for Sam Martin

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.

Avatar image for Charles

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?

Avatar image for adamgranthendry

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.

Avatar image for Vladislav S

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.

Avatar image for torrepreciado

torrepreciado on Dec. 7, 2021

Could this lesson be recorded back again with an actual troubleshoot of the problem and not just random guesses?

Avatar image for artemudovyk

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.

This “tutorial” is a mess, which only confuses people.

Become a Member to join the conversation.