Patching Code
00:00
In the previous lesson, I gave you a quick overview of the unittest framework built into Python’s standard library. Since testing is the primary use of Mock, in this lesson, I’ll show you how to use it with testing code.
00:13
You might recall these bullet points from before. Two lessons back, I showed you how you might pass a Mock object down into a function using a dependency injection-style approach.
00:22 In this lesson, I’m going to demonstrate the other option, which is replacing existing Python code with a mock. You typically do this with library calls, faking out, say, a network call, a database transaction, or other kinds of interactions.
00:36
The Mock module has a tool called patch() that allows you to replace arbitrary code with a Mock object. Since Python is a dynamic language, you can always change any object, modifying its methods or attributes, or swapping it for new code.
00:51 The process of dynamically replacing code like this is known as monkey patching. In case you’re curious, Wikipedia says the phrase came from an older term, “guerrilla patch”, as in guerrilla warfare, spelled with a U, meaning the patch being done was sneaky.
01:06 Guerrilla, as in warfare, is homonymous with the gorilla as in ooh, ooh, ooh, which may have become monkey over time.
01:15 The alternate theory is that monkey patching is based on the phrase “monkeying about”. This particular Wikipedia page is filled with footnotes and “citations needed” markers, so I don’t necessarily believe any of it.
01:28
Anyhow, before I got off on this banana-flavored tangent, I was talking about replacing code dynamically. It is so useful in testing that Python’s unittest module provides a mechanism for doing it.
01:40
You may have caught that I’ve been vague and not referring to patch() as a method or function. That’s because you can use it both as a decorator to your unit test method or as a context manager, patching code within the associated block.
01:54
Let’s go look at some new code, its associated test, and then use patch() to fake out some of its behavior.
02:01
This is picknum.py. If you’re using the provided sample code, it’s in the mock_patch/ subdirectory. The file has a single function called add_random().
02:11 What it does is add a random number between one and 10 to the argument passed in.
02:19
And this is test_pick.py, some test code for picknum. To use patch(), I need to import it, and like with the bork test code, I need to import the thing that I’m testing, which in this case is add_random().
02:33
First off, I’ll use patch() as a decorator. The argument to patch() is the thing that you’re patching. When testing with random numbers, you’ll get back a different value each time you call it, which makes asserting a value more challenging.
02:46
So here I’m patching the randint() call inside of picknum, replacing it with a Mock object. I want to say this one more time because it’s really important, and this is the trickiest part of using patch().
03:01
The argument here is the thing being monkey patched. Notice I’m not replacing any old randint(), I’m not replacing randint() in the module where it is declared.
03:12
I’m replacing the specific instance of randint() imported into picknum. Calls to randint() in other modules won’t be monkey patched; only this specific thing will be replaced.
03:27 This can be a little hard to wrap your head around. What makes it worse is if you get it wrong, you might still be monkey patching something, just not the something you meant to.
03:37 Then you’ll be scratching your head like a simian to understand why it isn’t working. I’ll stop with the monkey references eventually.
03:45
And here’s the second bit of trickiness. Normally, a test method expects only self as an argument, but the decorator here, which technically is sort of patching the method itself, is adding another argument to the call.
03:58
The second argument here is the mock object created by the patch specification. I’ve named ours mock_randint because that’s what is being patched.
04:08
mock_ or fake_ as a prefix on the thing you’re replacing is a good naming convention. Okay, once more from the top: the patch() decorator will replace the randint() in the picknum.py file with a mock object.
04:21
Then it passes that mock object into the test call as the second argument. So now when you call add_random(), the randint() call inside of it is the mocked object instead of the original randint() from the random module.
04:38
And since I don’t want a mock object returned from the call, I want a specific number, I’m using the return_value mechanism you’ve seen before, setting it to 41.
04:49
Now instead of generating a random number between one and 10, the randint() call, the fake one, will return 41 every time. I intentionally picked something that wasn’t between one and 10.
05:01
That way, if the patching goes wrong, I don’t accidentally randomly generate a value that I might be expecting. And then the rest of this test code is your typical test code: I call add_random(), passing in 1, it calls the patched randint(), which returns 41, giving me 42, which is then checked with assertEqual().
05:22
If the patch went wrong, I’ll get a number between 2 and 11. Or if there was a bug in the code, something that stopped randint() from being called, I’d get a different number and the assertion would fail.
05:34
To add a belt to my suspenders, I can also use the assert methods on the mock object itself, going from black-box, value-based testing to white-box. I know what it’s doing inside, and it should only be calling randint() once kind of testing.
05:52 And there you go. Test passes.
05:55
Now let me do that again, but this time I’ll use patch() as a context manager instead of a decorator. I’ve added a new test method, and inside it I’m using patch() as a context manager.
06:08
The argument to patch() is the same as before, but this time, instead of affecting the argument to the test method, I get at the mock as the context manager’s value.
06:18
Quick tangent. Up until now, you’ve only seen the mock object. There’s also a subclass of mock called MagicMock. Way back, several lessons ago, the call to len() failed because Mock doesn’t support a len() call.
06:31
That support is done through something known as a magic method, also known as dunder methods. When you use len(), it calls an object’s __len__().
06:40
That’s the double-underscore __len__ double-underscore method. As you might guess from the naming of the object, MagicMock actually implements most of the typical dunder methods.
06:49
If I had used a MagicMock in the early lesson, len() would have worked, as you might also have guessed by the fact that I’m bringing this up now.
06:58
patch() returns a MagicMock rather than a Mock. For our purposes, it doesn’t matter. But if you’re dealing with dunders, then it’s important to know when you’re using which. Now, where was I?
07:09 Inside the context block, the code is the same as the decorated test method before, and when I run it,
07:19 both tests pass. Now why would you use a context manager instead of decorating? Most of the time, it’s a style choice. Sometimes though, with complex tests, you might want to patch to be on for part of the test and off for another part.
07:33 Using a decorator approach means all or nothing for the method. Whereas with a context manager, you can arbitrarily turn it on or off as you see fit. Personally, I like the context manager, but that’s partially an aesthetic thing.
07:47 I always find decorators hard to read. I don’t like how they sit on top of a function. It clutters the signature, making it harder for me to visualize. But that could just be me.
07:58
So far, you’ve been monkey patching a function in a module. The patch.object() call is a helper for patching just one part of an object. You’ll learn about that next.
Become a Member to join the conversation.
