Understanding Unittest.Mock

Understanding How Patch Works

Your browser needs to be JavaScript capable to view this video

Try reloading this page, or reviewing your browser settings

Explanation of how patch works and how to do it properly. By understanding the internals of how it is implemented users discover “the magic” behind it, which helps avoiding many of the mistakes you see in intermediate and even advanced users when using patch.

Keywords

  • patch
  • internals
  • implementation
  • avoid
  • intermediate
  • advanced
  • users

About this video

Author(s)
Mario Corchero
First online
25 December 2018
DOI
https://doi.org/10.1007/978-1-4842-4413-5_8
Online ISBN
978-1-4842-4413-5
Publisher
Apress
Copyright information
© Mario Corchero 2019

Video Transcript

In this segment we’ll see how patch actually works. We’ll basically see what it… what it does with, uh, with, um, with the names that we patch and we’ll see one of the probably most common mistakes that people do when using patch. Let’s see how unittest.mock.patch actually works and that will help us prevent most of the mistakes that even advanced users will do when using patch. So here we see that we are importing patch, we’re importing requests, and we’re trying to just see how patch will affect this patching that we did before for request.session, right. So first thing we’re going to do is we’re going to print (requests.session) and, uh, we can see, well, that’s just, um… sorry, again, uh, .session, we can see that’s just a class within the request module, right. Now if we are within the context of patch and we do requests.session without an S, uh, as mock and we print here requests.session, again, right. Let’s… for the… for the sake of clarity, do here, um, say “patching started” and then here when we are done, let’s do patching finished. Okay. And then afterwards, we just pring(requests.session) again. Requests.session. Okay. So here we can see that, you know, first is just session within the requests.sessions then it suddenly becomes so much work which is the default class that patch uses to replace the original object, and then it’s back to being a session again. So what’s happening here is that when we… when we have… when we do this, like, this patch, um, uh, unittest is going to look to the requests module and then it’s going to basically just do setattr() sessions. So it’s basically going to do… it’s going to go to… let’s say that this was the requests.py. What’s going to happen is that within this context manager, at the beginning of it, patch is going to go a place where you can see session and it’s just going to do MagicMock. That’s all that’s going to happen. And then when this… when this is… when the context manager fix it, uh, it will set it back to original. And that is literally all that’s happening. Now even if this is a beautiful and simple and this brings us, uh, some issues. One of… one of them which I think is probably the most… the issue that people fall the most with unittest.mock when doing patches. The thing is like if we have the same code, okay, but we have a code that instead of using requests it’s using just session. So let’s say that instead of importing, uh, requests, we had to import… we had said, um, session. So we do from requests import session. If we do this, okay, if we do from requests import session, we are bringing this session named here. So the end is like if we did session = requests.session, right, you can see it kind of the same like we could… it would be the same as if we had imported requests and then we did session = requests.Session. Is doing something similar. Now the key is that because we’re here doing like patch (“requests.session”), what’s going to happen is as we saw before is that this is going to go to the request module and set it. But because in Python, everything are names, it’s going to say just the name that was brought into the requests module. But it’s not going to change this one, right, because this is set… so this session is just a reference the same object, yes, but it’s a name within a different namespace. So this, if we print this now, let me show you… oh sorry, we want to print the session, right, the session that wrote locally. So if we print this, you can see that it hasn’t changed this one. So this session which comes from requests.session hasn’t changed. Why is that, you may wonder. Well that’s because this patch(“requests.session”) has said, it patched it here. If we have this importing here, this would work, right? That that’s not usually how they called this. So you can see that it’s really important to understand how this works because this happens in code. Like, again, here you can see that we are calling patch but we are… we’re not patching anything, right, and this is… this is the reason why you see many people like trying to make… sometimes making test fail in purpose to verified that he’s mocking as expected because it’s really misleading. Let’s see. A sample of code. So let’s say we have source_code2. So let’s say we have this code, right, like, it’s exactly the same thing as before, but rather than importing requests and then doing request.sessions, it’s just… it’s just the importing sessions from requests and we have it here. If we use the same code that we had before, which if you remember, it was just from unittest.mock import patch, now import sour_code2. We have the payload which we said it should be something like “name”: “Secret user” and now we do with… let me put in the center. Uh, I’m going to add some space here. Then we do with patch(““request.Session”) as mock: and mock.return_value because that’s the constructor, you remember and get return_value.json.return_value is the payload. Okay. So we have that, right, we are mocking request.session. We’re changing it for something that we tell them what to return and then we do source_code2.get_user_name, um, it doesn’t matter what we pass here, pass (“mariocj89”). Okay. So if we do this and we run it, um, what’s going on. Oh, yeah, we want to print it, right? So this is returning them the real, uh, name, right, this is going to GitHub and that’s the way we want. And the reason why it’s, happening it’s because we have effectively patched requests.session but we have not matched source_code.session. We can see this if we print it. So if we print here, that’s… we also print requests.Session. So you can see that, um, like, um, this thing, source_code2.session, still requests.session.session. Whilst this requests.session has effectively changed by a MagicMock. This is because what’s happening is that at import time, we are bringing the names of different places. And when we patch, we want to make sure that we are patching the real object. Well, we are passing the object that we want to be changed. So instead of doing patch requests.session, what we should be doing is source_code2.session. If that’s not the place where the object originally live, there’s a place where it’s being used, so we want to change the name like the reference to session where we wanted to be changed. So now with this, it is effectively secret user. And you know with that, we can… we need to understand and we need to always make sure that when we are patching something, we are patching where it’s being used not where it’s being originated. This is probably the most, like, biggest fault of, uh, using patch. And, um, like if… but if we just remember how this works, how unittest.mock works, it’s easy to understand that. The problem is when we do… when we… when we are patching something, we are just doing setattr() if we do a setattr() source_code2, then this is going to be changing session in this file. If we do it in the request, it’s going to change in this file. Now something that’s interesting is that some… it feels like sometimes it works, sometimes it doesn’t right and that’s because, you know, it depends on how we are importing it in the file, like, well here, it depends on some other things because, again, this is just a set that are happening in a specific namespace.