Understanding Unittest.Mock

Restricted

Your browser needs to be JavaScript capable to view this video

Try reloading this page, or reviewing your browser settings

This segment shows one of the most common mistakes when using mocks, the lack of control on the child mock feature. By using spec and seal, a new feature in Python3.7 we ensure our mocks work as we expect them to do.

Keywords

  • mock
  • validation
  • seal
  • spec
  • autospec

About this video

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

Video Transcript

In this segment we’re going to see the issues of automatic child mock creation. Additionally, we’ll see how to restrict our mocks by using spec and seal. We have already seen how handy it is the create testing levels by using unittest.mock. And we’ve also seen, uh, what MagicMocks are and how handy it is to rely on this feature of mock that allows you to create, you know, child mocks, and inner mocks mocks automatically Which is a really nice feature, but at the time as we’re going to see here, it’s one of the main issues of confusion of mocks when writing tests using unittest.mock. And the reason for it is that, uh, in this situation, we have… we have definition get_user_name and we have, you know, this mock we are calling. And notice something that now the function is not being called get. It’s called get_url. And the problem is because even if we have to find these get, like, this path or fake_session where you called get and then on the return value called JSON. You have that specific one. Who do you have to find is, it can happen that, uh, you pass the fake_session here and was going to be called this get_url rather than get. And you would expect this to fail, right? Because that’s not how the session look like. But the problem is that before… because of this feature, of automatically creating, uh, mocks when you call a new attribute, this is just going to give you a MagicMock. And these kind of issues are really hard to trace, right. Because now you need to fully understand what’s happening here, um, in the way that the mock is being called. And you end okay getting a really helpful message out of it. I said… I think from, like, from uses of unittest.mock, I think this is one of the main issues, uh, that people find. And the real reason here is that you, you know, you want this fake session. It’s supposed to just be, um, your testing double session, right. Now how can we prevent it? How can we make sure then, you know, this fake_session is going to… is going to satisfy the original implement… the original interface of it. And we can do that using spec. So let’s say that get session is was Let’s do it the other way around. Let’s say that we mock get_url and we have here get right. So this still produces the same issue. Now let’s say that, uh, we want, uh, we want to have the same… we want to make sure that our mock follows the interface. And to do so, all we have to do is on our unittest, we, from requests we import session. And then when we create this MagicMock or mock, we can say, “Hey, follow the specification of this API.” And to do so, you just do spec=Session and you do that. And now when you are going to do that, it’s going to tell you, A, you’re telling me to go through get_url, but get_url is not part of my interface. This spec, it’s really powerful way to prevent this kind of issues. Now it’s not all great because, uh, Python, you know, we have… we have typing now, type hands. But it’s not… it’s not… it’s not, like, uh, it doesn’t have a hard type system. What that means is that this spec works only for the first level of it. What I mean by that is that if we fix it and say get_url, but then instead of JSON, we make a mistake and here we’d say get_json, we’re back to the same issue. Now when we call this, we are back to the same issue. And you know, you might be thinking “Hey, something I can do you is rather than relying in this feature, you might… you might be thinking too, instead start to create mocks one by one as we saw in one of our first segments and then specs, uh, the spec to each of them. This is rather painful and I don’t personally recommend it because it like you called and it’s going to be more than, you know, the test, you’re actually going to test. So we have some options here, I mean, it’s not that spec’s bad since spec has its function. It allows you to set the the API on that. But if you have, uh, you know, if you are trying to hit… you’re trying to test the function that’s going to call, um, like the result of a function and then the result of the function is going to call, uh, the attribute, things like that. Spec is probably not your best friend. Also something that spec is useful for, um, it’s worth mentioning, that allows you to pass the isinstance check. So if you have a mock, okay, and now you do, um, uh, isinstance(m, int), okay. m… well, let’s use the session to keep everything. Uh, so if you do that, you’re going to get the false. If the mock has the spec. Sent. This will pass. So this is also useful if you’re trying to pass a testing double into an API that is doing this kind of checks, right. Okay, so we said, you know, spec has its use case and it has its place, but what do we do in a situation like the one that we saw here. And the answer is a new method that was introduced in Python 3.7 called seal. Um, what seal allow us to do is, um, freeze the API off the mock at the time that we’ll call it. So what… if we think of it as what’s the issue here, right. Why are we bothered about get_json. The problem is that this really nice feature of generating child mocks is extremely useful to define our mock, but it’s not useful anymore here. At that point when we’re repressing here, we don’t want this feature to be keep… to keep enable. So what we can do is we can say seal(fake_session) and from this point on, no new entries will generate it. We do import of course, unittest.mock import seal. Now when we do this, okay, we are making sure that no new things will be called on it and this is recursive. So now we do seal, we have the fake_session. Fake_session has been already cleared here, we need, of course, to restart this because we want to create any fake_session, so we create a new fake_session, we’re going to seal it and we’re going to call get_user_name. And we can see here that this fails within get_user_name when trying to call mock.get().json. And this is true because what we define is the mock.get.return_value which is what they’re calling it then get_json. So this mock doesn’t have that API defined. By just using seal, we prevent… uh, we prevent those from going silent and not understanding what’s going on. Um, as a note here, because seal will recursively set everything out. If you don’t want it to do it for a reason for a part of your mock, you can just assign that you won’t do it. And what I mean with this is you can do mock equals, um, say we have a mock, right, and then we say… we can define API of it by mock.attr1.attrb = 1 and mock.attr2. … well, this is just going to be a new mock. And if you do this, okay, if you do this, you will be able to… um, sorry, with… you need to pass a name or spec for it not because be consider, um, child. If you do this and you say (name=“this_is_not_a_child _of_mock”). Now by doing this, if you now seal (mock), that function is going to make sure that no new attributes will be created in everything that directly inherits… well, direct… is a child of mock. So if you now try to do mock.attr1.new_attribute, this will fail, right. Uh, if you try to do mock.attr1…. well, mock.attr2.new_attribute, this will succeed because, um, we make this not the child of it by creating a mock, assigning it to it with different name, which we’ll see in the future, uh, segment what name does for us. But, yeah, this just allowed us to seal the mock, make sure that they don’t appear… that new attributes don’t appear after we don’t want to and we know how to disable it for a part of the branchif we’re interested to. And with this, we can make sure that we can pass, uh, testing doubles to our tests and they will behave just on the way that we define before that call.