Avoiding Weird Bugs via Specifications
00:00
In the previous lesson I showed you how to use patch() when dealing with dates and times. In this lesson, I’ll cover how to configure your mock objects more specifically.
00:09 By default, any method you call on a mock object is valid and returns a new mock object. This can be a bit of a problem if you’ve made a typo; an incorrectly spelled call will also work.
00:22
Your tests might not behave the way you want because you’ve accidentally set .return_value on a method that isn’t called because you fat-fingered the name.
00:30
You can get around this problem by explicitly telling Mock just what is and is not valid. You do that by providing a specification. Let’s head back to the REPL to configure some mock objects.
00:43
I’m going to revisit my example that used a Path object to read the messages text file
00:57 and that’s how it’s supposed to work. Now let’s mock it out.
01:06
Say I was faking out this path. I would call .read_text() on it,
01:14 which of course returns a new instance of a mock object. So far this should be so much déjà vu, but did you catch that? I forgot the E. Had I done the same thing on the real object, I would’ve got an error.
01:29 Since I’m using a recent version of Python, I even get a “Did you mean” message, and yes, yes, I did mean. When you construct a mock object, you can configure it to avoid this problem.
01:42
The spec attribute takes a list of names of methods which are allowed.
01:51
.read_text() works, but at this time, if I forget the E, I now get an error. It even triggers the same “Did you mean” mechanism. Instead of giving it a list of method names, you can also give an object.
02:08
By providing Path as a spec, I get back a mock object with the same methods that Path has.
02:17
So .read_text() works, but without the E I get an error. I like this way better than providing a list of strings as a specification because you could always put a typo in those strings as well, whereas this way guarantees it’s going to be right.
02:35
If instead of mocking an object, you’re mocking a module, there is a helper function that makes your life easier. It’s called create_autospec(), and it works similar to passing an object in as a spec.
02:49
First I’m going to need a module. Then I call create_autospec(), passing the module in,
03:01
and the result is a mock object representing the module, which in this case includes a MagicMock Path object.
03:14 Like with the spec on an object, if I try to invoke something that isn’t there, I get an error, as it wasn’t part of the autospec.
03:25
You can do something similar with arguments to patch(). I’m importing a new module to fake out. Then inside of the context manager, I specify that I’m mocking out random.
03:41
Remember, __main__ is the execution module for the REPL. So I’m telling patch() to mock out the random module that I just imported into this REPL.
03:51
By setting the autospec=True argument, I’m telling patch() to spec out the fake using the original as its pattern to mimic, which is pretty clever.
04:10
So inside the context block, the call to .randint() returned a MagicMock since it’s a real function and that got included because of the autospec.
04:19
But then the call to .not_a_thing() which isn’t a thing in the random module caused an exception, as you would expect. I highly recommend that you use the specification feature of Mock when you’re testing.
04:31 It could save you from having to track down some weird bugs inside of your test code. The last lesson is next. In it I will summarize the course and point you at some further resources.
Become a Member to join the conversation.
