Patching Objects
00:00
In the previous lesson, I introduced you to patch(), which you can use to dynamically replace a function in a module with a mock object. patch() has a sibling, patch.object(), which helps you to patch a single method on a Python object instead.
00:15
Instead of using patch(), you can use patch.object(), which is very similar, but allows you to patch a method on the instance of an object instead of replacing something in a module.
00:25
This gives you finer-grain control over what you are replacing. If you’re coding along, the next chunk of code and its corresponding test is in the patch_object/ directory.
00:37
This is pickalpha.py to go along with picknum from before. Like with the previous example, I’m going to be replacing methods that deal with randomization.
00:47 This time, though, I’m importing the module instead of a specific function. I think you’ve heard me mention this before. Everything in Python is an object. That goes for modules you’ve loaded as well.
00:57
So the object I’m going to patch is the reference to the module in this case. The get_letters() function returns a tuple of two letters.
01:07
chr() returns a character corresponding to an ASCII numeric value. My hard-coded 97 is the code for the small letter a.
01:15
So the first item in the tuple is going to be some number of letters past a, where some is the argument passed in, plus a random value between 1 and 10.
01:25
The second item in the tuple is similar, but instead of using a random value between 1 and 10, this line uses the random module’s choice() function, which randomly chooses one of the values from its iterable.
01:38
In this case, that would be 0, 1, 2, or 3,
01:43 and here’s the corresponding test case. In addition to importing the function I’m testing, I also need to import the object that I want to patch. This is kind of like the name trickiness from before.
01:56
I’m not importing the general random module, but the specific reference that got imported into pickalpha. I do this so that I can patch it.
02:06
I’m using the context manager approach and calling patch.object() instead of just patch(). There are a couple of new things in this call.
02:14
First, although patch() itself is callable, well, everything in Python is an object, so I can have a callable on the data aspect of patch().
02:23
I’m not sure why they did it this way, personally. I would’ve just called it patch_object(), but hey, what do I know? The first argument to patch.object() is the thing being patched, which in our case is the reference to the random module inside of pickalpha.
02:40
Like before, I need to say what I’m patching. Here I’m naming choice(). choice() itself is a function in the module, but when you treat a module like an object, the function inside of it is yet another thing that you can monkey-patch.
02:55
Like in the previous lesson, I want to hard-code the return value to remove any randomness from the test. Instead of doing that inside of the context block, I can use the side_effect argument to patch.object().
03:06
Since I want to hard-code a value, I’m using a lambda as the callable function for side_effect, which in this case is going to return -32.
03:16
I chose -32 because, one, it isn’t a value that can be generated randomly from the code, and two, it’s the difference between little a and capital A in the ASCII table. Here I’m calling our actual function, passing in 0 as the argument, and so that you can see the results, I’m printing out the first value returned, and the second value returned.
03:39
And then I’m printing out references to the randint() and choice() functions, one of which I just monkey-patched, and then below all that I call assertEqual() because, well, this is test code, so that just makes it a little more realistic.
04:00
Remember, I only patched choice(), so the randint() call actually called randint(), so if you’re running this yourself, the printout of the first returned value in the tuple might not be g.
04:11
It could be something else. Then since choice() got patched, the mock got called, invoking the side_effect, and instead of a random value selected from 0 to 3, you get -32, resulting in capital A.
04:25
Just to hit that point home, randint() is a method on the random module object, while choice() is a MagicMock.
04:33
I intentionally used a module to demonstrate the power of this technique, but it would work equally well with any other Python object. For example, if you wanted to replace only one of an object’s methods and leave the rest of them alone. One of the most common uses of Mock in tests is to deal with dates and times.
04:53
Like with random, you need a predictable value in your test. The next lesson doesn’t actually have anything new in it. You’ll have seen it all before, but seeing as how “How do I mock the datetime module?” is one of the most common questions for Mock, I figured there’s value in some review with this specific case.
Become a Member to join the conversation.
