In order to ensure that application logic is valid, dependable, and efficient, you must write tests before releasing your code. Yet your tests’ worth depends on how successfully they prove these conditions. Writing useful tests is challenging due to challenges such as complicated logic and unforeseen dependencies. These challenges may be surmounted with the aid of the unittest.mock Python mock object package. The knowledge gained from this article will allow you to:
-
Create Python mock objects using
Mock
- Assert you’re using objects as you intended
- Inspect usage data stored on your Python mocks
- Configure certain aspects of your Python mock objects
-
Substitute your mocks for real objects using
patch()
- Avoid common problems inherent in Python mocking
To begin, you will get an understanding of what mocking is and how it may help you.
tests.
What Is Mocking?
In the context of testing, a fake item stands in for and mimics its real-world counterpart. It’s an effective and flexible method for enhancing the reliability of your testing.
Mock objects in Python may help you manage how your code acts when testing.
If, for instance, your code performs HTTP queries to third-party services, then the reliability of your tests will depend on the third-party services working as intended. Intermittent failures in your test suite may be the result of a momentary change in the behaviour of these external services.
In light of this, it is recommended that you do tests on your code in a sandpit. You could reliably imitate interruptions or successful answers from external services by substituting a fake object for the real request.
Some portions of your codebase may be challenging to test. Places like hard-to-satisfy if statements and unless blocks fall into this category. If you want to boost your code coverage in certain spots, using Python fake objects may assist you direct the code’s execution to get there.
Mock objects are useful since they let you see where and how you’re using the actual objects in your code. Information on the object’s use, including but not limited to
as:
- If you called a method
- How you called the method
- How often you called the method
Learning how to utilise a fake object requires first comprehending its purpose.
Learn the ins and outs of Python’s mock
objects.
The Python Mock Library
Python’s unittest.mock is the mock object library. It makes it simple to include mocks into test scenarios. In Python 3.3 and beyond, unittest.mock is part of the default distribution. The official backport of the library must be installed if you are using an earlier version of Python. To do this, get mock from PyPI.
:
$ pip install mock
You may use the Mock class from the unittest.mock library to simulate the behaviour of other classes and objects in your code. Mock provides fantastic adaptability and useful information. This, together with its subclasses, should cover your bases for mimicking in Python during testing.
The package also has a handy method called patch() that may be used to swap out actual objects with Mock ones. Patch() may be used as either a decorator or a context manager, allowing you to decide in what context the mocked object will be used. After the specified scope ends, patch() will clean up your code by restoring the original versions of the objects that were mocked.
Lastly, unittest.mock offers answers to some of the problems that arise while mocking objects.
You have gained a deeper comprehension of mocking and the library you’ll be utilising to implement it. Let’s go in and check out the capabilities of the unittest.mock
offers.
The
The Mock foundation class is provided by the unittest.mock library. Because to its adaptability, Mock’s potential applications are almost endless.
Create a brand new Mock object.
instance:
>>>
>>> from unittest.mock import Mock
>>> mock = Mock()
>>> mock
<Mock id='4561344720'>
It’s time to put the Mock to work, subbing it in for real objects in your code. You may either redefine a function to take it as an argument or call another function to accomplish this.
object:
# Pass mock as an argument to do_something()
do_something(mock)
# Patch the json library
json = mock
The code substitution object (Mock) must be visually identical to the original object. If not, your programme won’t be able to substitute the Mock for the real thing.
If you’re simulating the json library in your code and making calls to dumps(), you must include dumps() in your Python fake object.
Follow along to find out Mock’s response!
challenge.
Lazy Attributes and Methods
A Mock must act as a stand-in for the real thing. Such adaptability is made possible by the dynamic creation of characteristics at the time of use.
:
>>>
>>> mock.some_attribute
<Mock name='mock.some_attribute' id='4394778696'>
>>> mock.do_something()
<Mock name='mock.do_something()' id='4394778920'>
Mock is flexible enough to stand in for any object, since it can generate properties on the fly.
To continue with the preceding example, using dumps() on a Python fake object will cause it to generate a function whose interface is identical to that of the json library being mocked.
interface:
>>>
>>> json = Mock()
>>> json.dumps()
<Mock name='mock.dumps()' id='4392249776'>
Two important features of this parody of dumping ()
:
-
Unlike the
real
dumps()
, this mocked method requires no arguments. In fact, it will accept any arguments that you pass to it. -
The return value of
dumps()
is also a
Mock
. The capability of
Mock
to
recursively
define other mocks allows for you to use mocks in complex situations:
>>>
>>> json = Mock()
>>> json.loads('{"k": "v"}').get('k')
<Mock name='mock.loads().get()' id='4379599424'>
Mocks may be used to many different uses since each mocked method returns a Mock.
Mocks are adaptable, but they also provide valuable insight. In the next section, you’ll discover how to better comprehend your code by using mocks.
better.
Assertions and Inspection
Use information is recorded in mock instances. You may check to verify whether a method was called, how it was called, and so on. There are two primary applications for this data.
The first option is to claim that an object was really utilised in your code.
expected:
>>>
>>> from unittest.mock import Mock
>>> # Create a mock object
... json = Mock()
>>> json.loads('{"key": "value"}')
<Mock name='mock.loads()' id='4550144184'>
>>> # You know that you called loads() so you can
>>> # make assertions to test that expectation
... json.loads.assert_called()
>>> json.loads.assert_called_once()
>>> json.loads.assert_called_with('{"key": "value"}')
>>> json.loads.assert_called_once_with('{"key": "value"}')
>>> json.loads('{"key": "value"}')
<Mock name='mock.loads()' id='4550144184'>
>>> # If an assertion fails, the mock will raise an AssertionError
... json.loads.assert_called_once()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 795, in assert_called_once
raise AssertionError(msg)
AssertionError: Expected 'loads' to have been called once. Called 2 times.
>>> json.loads.assert_called_once_with('{"key": "value"}')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 824, in assert_called_once_with
raise AssertionError(msg)
AssertionError: Expected 'loads' to be called once. Called 2 times.
>>> json.loads.assert_not_called()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 777, in assert_not_called
raise AssertionError(msg)
AssertionError: Expected 'loads' to not have been called. Called 2 times.
.assert called() guarantees that the mocked function was invoked, while.assert called once() verifies that this occurred just once.
There are variations of both assertion functions that expose the mimicked function’s parameter list for inspection.
method:
-
.assert_called_with(*args, **kwargs)
-
.assert_called_once_with(*args, **kwargs)
You must pass these assertions by calling the mocked method with the identical arguments that you would send to the real
method:
>>>
>>> json = Mock()
>>> json.loads(s='{"key": "value"}')
>>> json.loads.assert_called_with('{"key": "value"}')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 814, in assert_called_with
raise AssertionError(_error_message()) from cause
AssertionError: Expected call: loads('{"key": "value"}')
Actual call: loads(s='{"key": "value"}')
>>> json.loads.assert_called_with(s='{"key": "value"}')
An AssertionError was thrown by json.loads.assert called with(‘”key”: “value”‘) because you attempted to call loads() with a keyword argument rather than a positional argument. This assertion is true if and only if you call json.loads.assert called with(s='”key”: “value”‘).
Second, your app’s utilisation of an external resource may be scrutinised through hidden properties.
object:
>>>
>>> from unittest.mock import Mock
>>> # Create a mock object
... json = Mock()
>>> json.loads('{"key": "value"}')
<Mock name='mock.loads()' id='4391026640'>
>>> # Number of times you called loads():
... json.loads.call_count
1
>>> # The last loads() call:
... json.loads.call_args
call('{"key": "value"}')
>>> # List of loads() calls:
... json.loads.call_args_list
[call('{"key": "value"}')]
>>> # List of calls to json's methods (recursively):
... json.method_calls
[call.loads('{"key": "value"}')]
These properties may be used in unit tests to ensure that objects perform as expected.
You can now make fakes and look at how they’re being used. The next thing you’ll learn is how to modify mocked methods to make them more relevant during testing.
environment.
Managing a Mock’s Return Value
Mocks are useful during testing because they allow you to simulate real-world conditions and observe how your code responds. One approach is to define the value that a function should return. Let’s look at an illustration to better understand this.
Create a new Python document and name it my calendar.py. Put in is weekday(), a method that checks the current date against Python’s datetime library to see whether it’s a workday. To conclude, a test confirming the function’s correctness must be developed.
expected:
from datetime import datetime
def is_weekday():
today = datetime.today()
# Python's datetime library treats Monday as 0 and Sunday as 6
return (0 <= today.weekday() < 5)
# Test if today is a weekday
assert is_weekday()
Because you’re checking to see whether today is a Wednesday, the outcome will vary depending on the day of the week you choose to do the test.
test:
$ python my_calendar.py
In the absence of any output from this command, the assertion is considered to have passed. The command will fail with an AssertionError if it is executed on a weekend.
:
$ python my_calendar.py
Traceback (most recent call last):
File "test.py", line 9, in <module>
assert is_weekday()
AssertionError
The findings of your testing should be reliable and consistent. During testing, Mock may help you remove any lingering doubts from your code. To do this, you may use a mock datetime and provide whatever date you choose as the.return value for.today().
choose:
import datetime
from unittest.mock import Mock
# Save a couple of test days
tuesday = datetime.datetime(year=2019, month=1, day=1)
saturday = datetime.datetime(year=2019, month=1, day=5)
# Mock datetime to control today's date
datetime = Mock()
def is_weekday():
today = datetime.datetime.today()
# Python's datetime library treats Monday as 0 and Sunday as 6
return (0 <= today.weekday() < 5)
# Mock .today() to return Tuesday
datetime.datetime.today.return_value = tuesday
# Test Tuesday is a weekday
assert is_weekday()
# Mock .today() to return Saturday
datetime.datetime.today.return_value = saturday
# Test Saturday is not a weekday
assert not is_weekday()
The.today() function is a counterfoil in this case. By setting the mock’s.return value to a precise date, you have eliminated the discrepancy. This ensures that the date and time you provide is what is returned when you call.today().
The first check is validating that Tuesday really is a workday. You make sure that Saturday is not a workday in the second test. You may now run your tests on any day of the year since you are in complete control of the object’s behaviour thanks to your mocking of datetime. Although this is a great example of how to use Mock to imitate a datetime, a far better library already exists: named
gun that freezes.
Mocking a function’s return value may not always be sufficient when developing your tests. This is because functions are often more involved than a straight line of reasoning.
It might be useful to have functions return various results or throw exceptions depending on how many times they are called. The.side effect tag allows for this.
.
Managing a Mock’s Side Effects
By defining the side effects of a mocked function, you may manipulate the behaviour of your code. What occurs when a call to the mocked function is made is specified by its.side effect.
Insert a new function into my calendar.py and run it to see if it works.
:
import requests
def get_holidays():
r = requests.get('http://localhost/api/holidays')
if r.status_code == 200:
return r.json()
return None
In order to get a list of holidays, get holidays() contacts the server on the localhost. get holidays() will return a dictionary if the server replies correctly. Otherwise, the method will exit with a status of None.
By altering requests.get.side effect, you may see how get holidays() handles a connection timeout.
Just the relevant portions of my calendar.py will be shown here. Using Python’s unittest, you’ll construct a test scenario.
library:
import unittest
from requests.exceptions import Timeout
from unittest.mock import Mock
# Mock requests to control its behavior
requests = Mock()
def get_holidays():
r = requests.get('http://localhost/api/holidays')
if r.status_code == 200:
return r.json()
return None
class TestCalendar(unittest.TestCase):
def test_get_holidays_timeout(self):
# Test a connection timeout
requests.get.side_effect = Timeout
with self.assertRaises(Timeout):
get_holidays()
if __name__ == '__main__':
unittest.main()
Because get() now has a side effect, you may use.assertRaises() to ensure that the get holidays() method throws an exception.
Check your hypothesis with this simple test.
test:
$ python my_calendar.py
.
-------------------------------------------------------
Ran 1 test in 0.000s
OK
To provide some variation to your mocked method’s behaviour, set.side effect to a function that Mock will call on your behalf. Mock uses.side effect’s parameters and return value.
function:
import requests
import unittest
from unittest.mock import Mock
# Mock requests to control its behavior
requests = Mock()
def get_holidays():
r = requests.get('http://localhost/api/holidays')
if r.status_code == 200:
return r.json()
return None
class TestCalendar(unittest.TestCase):
def log_request(self, url):
# Log a fake request for test output purposes
print(f'Making a request to {url}.')
print('Request received!')
# Create a new Mock to imitate a Response
response_mock = Mock()
response_mock.status_code = 200
response_mock.json.return_value = {
'12/25': 'Christmas',
'7/4': 'Independence Day',
}
return response_mock
def test_get_holidays_logging(self):
# Test a successful, logged request
requests.get.side_effect = self.log_request
assert get_holidays()['12/25'] == 'Christmas'
if __name__ == '__main__':
unittest.main()
You started out by making.log request(), which accepts a URL, logs some stuff using print(), and then returns a Mock response. So, before using get holidays(), you must change the.side effect of get() to.log request(). After passing its parameters to.log request(), get() then delivers the result as seen when the test is executed.
well:
$ python my_calendar.py
Making a request to http://localhost/api/holidays.
Request received!
.
-------------------------------------------------------
Ran 1 test in 0.000s
OK
Great! Instances of print() were able to record accurate data. The holidays dictionary was returned by get holidays(); alternatively, side effect might be an iterable. Either return values or exceptions, or both, must make up the iterable’s elements. Each invocation of the mocked method will result in the iterable producing the next value in the iterable. You may verify, for instance, that retrying after a Timeout still results in a successful
response:
import unittest
from requests.exceptions import Timeout
from unittest.mock import Mock
# Mock requests to control its behavior
requests = Mock()
def get_holidays():
r = requests.get('http://localhost/api/holidays')
if r.status_code == 200:
return r.json()
return None
class TestCalendar(unittest.TestCase):
def test_get_holidays_retry(self):
# Create a new Mock to imitate a Response
response_mock = Mock()
response_mock.status_code = 200
response_mock.json.return_value = {
'12/25': 'Christmas',
'7/4': 'Independence Day',
}
# Set the side effect of .get()
requests.get.side_effect = [Timeout, response_mock]
# Test that the first request raises a Timeout
with self.assertRaises(Timeout):
get_holidays()
# Now retry, expecting a successful response
assert get_holidays()['12/25'] == 'Christmas'
# Finally, assert .get() was called twice
assert requests.get.call_count == 2
if __name__ == '__main__':
unittest.main()
If you try to use get holidays() for the first time, get() will throw a Timeout. When called again, the procedure provides a usable holidays dictionary. The effects here are in the same sequence as the ones supplied to.side effect.
Both.return value and.side effect are directly settable on a Mock. Python fake objects need some leeway in attribute creation, thus there is a better method to set them up.
settings.
Configuring Your Mock
A Mock may have some of its behaviours set up by the user. .side effect,.return value, and.name are all members that may be modified. A Mock is configured when it is first created or when.configure mock() is used.
At the time of Mock initialization, you may customise it by setting several properties.
object:
>>>
>>> mock = Mock(side_effect=Exception)
>>> mock()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 939, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 995, in _mock_call
raise effect
Exception
>>> mock = Mock(name='Real Python Mock')
>>> mock
<Mock name='Real Python Mock' id='4434041432'>
>>> mock = Mock(return_value=True)
>>> mock()
True
Certain properties, such as.name, may only be set via. init__() or.configure mock(), while others, such as.side effect and.return value, can be set directly on the Mock instance. Instance-specific settings, such as the Mock’s.name, will result in a different response.
result:
The name attribute,
>>>
>>> mock = Mock(name='Real Python Mock')
>>> mock.name
<Mock name='Real Python Mock.name' id='4434041544'>
>>> mock = Mock()
>>> mock.name = 'Real Python Mock'
>>> mock.name
'Real Python Mock'
.name, is often used by objects. In contrast to.return value and.side effect, Mock does not let setting this value on the instance. Instead of setting your mock, accessing mock.name will generate a.name attribute.
By using.configure mock, an existing Mock may be modified ()
:
>>>
>>> mock = Mock()
>>> mock.configure_mock(return_value=True)
>>> mock()
True
You may also set the properties of your mock object in Python by unpacking a dictionary into the.configure mock() or Mock. init__() methods. With the use of Mock setups, you may streamline an existing
example:
# Verbose, old Mock
response_mock = Mock()
response_mock.json.return_value = {
'12/25': 'Christmas',
'7/4': 'Independence Day',
}
# Shiny, new .configure_mock()
holidays = {'12/25': 'Christmas', '7/4': 'Independence Day'}
response_mock = Mock(**{'json.return_value': holidays})
The ability to create and alter Python fake objects is now available. Mocks are another tool you may use to manage how your programme operates. Mocks have been used so far in the same module as your tests, either as parameters to functions or by patching objects.
The next step is to learn how to use your mocks in place of the real thing.
modules.
unittest.mock’s patch() function, which searches for an object in a specified module and substitutes it with a Mock, is a powerful technique for mimicking objects.
To mimic a target, you often use a decorator or context manager like patch() to contain the mocking activity.
object.
Patch() is a function decorator that may be used to temporarily replace a real object with a fake one for the length of a test function.
Modify your my calendar.py file by splitting out the logic and tests so you can understand how this works.
files:
import requests
from datetime import datetime
def is_weekday():
today = datetime.today()
# Python's datetime library treats Monday as 0 and Sunday as 6
return (0 <= today.weekday() < 5)
def get_holidays():
r = requests.get('http://localhost/api/holidays')
if r.status_code == 200:
return r.json()
return None
These procedures have been moved out of the test file and into their own. Create a new file named tests.py to recreate your test suite.
You have so far “monkey patched” preexisting objects in their source file. The term “monkey patching” refers to the practise of dynamically swapping out one item for another. In my calendar.py, you will now use patch() to swap out the objects.
:
import unittest
from my_calendar import get_holidays
from requests.exceptions import Timeout
from unittest.mock import patch
class TestCalendar(unittest.TestCase):
@patch('my_calendar.requests')
def test_get_holidays_timeout(self, mock_requests):
mock_requests.get.side_effect = Timeout
with self.assertRaises(Timeout):
get_holidays()
mock_requests.get.assert_called_once()
if __name__ == '__main__':
unittest.main()
You fixed requests locally using a Mock at first. My calendar.py must now import the requests library into tests.py.
You did this by passing the path of the object you wanted to modify to the patch() decorator. My calendar.requests was the intended route, which includes the module’s name and the object’s name.
You also added a new test function parameter that you created. The mocked object is sent into your test through this argument to the patch() function. The mock may then be adjusted or assertions made.
To make sure everything is functioning properly, run this test module.
expected:
$ python tests.py
.
-------------------------------------------------------
Ran 1 test in 0.001s
OK
In a nutshell, the patch() function produces a new instance of
MagicMock is a special kind of Mock. Since it includes the most majority of commonly used mockup
the mystical methods like. len (),. str__(), and. iter__() with sensible defaults.
In this scenario, the decorator patch() proved to be really useful. When used in the right context, patch() may make code more legible, more efficient, or easier to work with.
manager.
Patch() is useful not just as a decorator, but also as a context manager. If you’re considering using a context manager, you may find that the
following:
- You only want to mock an object for a part of the test scope.
- You are already using too many decorators or parameters, which hurts your test’s readability.
Python’s with module is required for usage with patch() in context management.
statement:
import unittest
from my_calendar import get_holidays
from requests.exceptions import Timeout
from unittest.mock import patch
class TestCalendar(unittest.TestCase):
def test_get_holidays_timeout(self):
with patch('my_calendar.requests') as mock_requests:
mock_requests.get.side_effect = Timeout
with self.assertRaises(Timeout):
get_holidays()
mock_requests.get.assert_called_once()
if __name__ == '__main__':
unittest.main()
Patch() restores the original object in place of the simulated one after the test leaves the with statement.
You’ve been mocking whole objects up until now, but there may be times when you only want to mock a specific feature.
object.
Patching an Object’s Attributes
What if, instead of mocking the whole object, you only want to mimic a certain method? Patch.object() will allow you to accomplish this.
To simulate the behaviour of requests.get(),.test get holidays timeout() require only set.side effect to Timeout.
:
import unittest
from my_calendar import requests, get_holidays
from unittest.mock import patch
class TestCalendar(unittest.TestCase):
@patch.object(requests, 'get', side_effect=requests.exceptions.Timeout)
def test_get_holidays_timeout(self, mock_requests):
with self.assertRaises(requests.exceptions.Timeout):
get_holidays()
if __name__ == '__main__':
unittest.main()
Just get() has been mocked, rather than all of requests, in this case. In every other respect, nothing has changed.
The arguments that patch() uses are also accepted by object(). The first argument is not the path to the target, but rather the object itself. The second input is the mocked property of the target object. Like patch(), object() may be used to control the current context. The patch() function may be used on dictionaries as well as objects and attributes.
As in, patch.dict().
Mocking objects in other modules requires familiarity with the patch() function. Unfortunately, there are situations when the trajectory of the item of interest is unclear.
is.
Where to Patch
If you don’t know where the object you want mocked is stored, you can receive an unexpected response from patch() if you instruct it to search there.
Thus, in my calendar.py, I am using patch to simulate the call to is weekday() ()
:
>>>
>>> import my_calendar
>>> from unittest.mock import patch
>>> with patch('my_calendar.is_weekday'):
... my_calendar.is_weekday()
...
<MagicMock name='is_weekday()' id='4336501256'>
To begin, you must bring in my calendar.py. Then you replace the real is weekday() with a Mock. Great! Currently, no unforeseen issues have arisen.
Let’s make a little adjustment to this example by bringing in the function
directly:
>>>
>>> from my_calendar import is_weekday
>>> from unittest.mock import patch
>>> with patch('my_calendar.is_weekday'):
... is_weekday()
...
False
Take into account that the value Yes or False may appear in the console, depending on the date you access this lesson. What’s crucial is that, unlike previously, the output is not a Mock.
Take note that the result of executing is weekday() differs even if the location you gave to patch() has not changed. The variation results from a modification to the function’s import.
If you import is weekday from my calendar, the actual function will be bound to the current scope. Because you have a local reference to the un-mocked function already, it doesn’t matter whether you patch() the function afterwards or not.
As a general rule, it’s a good idea to patch() the object at the lookup location.
In the first scenario, you may successfully spoof the’my calendar.is weekday()’ method by referencing the my calendar module. In the second, is weekday() is a local variable. Because you’re calling a local function, it’s only fair to spoof that scope.
function:
>>>
>>> from unittest.mock import patch
>>> from my_calendar import is_weekday
>>> with patch('__main__.is_weekday'):
... is_weekday()
...
<MagicMock name='is_weekday()' id='4502362992'>
You should now have a solid understanding of patch() and everything it can do. You now know where to patch() objects and attributes and how to do it.
After that, you’ll see how unittest.mock addresses some of the most often encountered issues with object mocking.
provides.
Common Mocking Problems
Many issues may be brought into your tests by using mocking objects. Certain issues arise because of the nature of mocking itself, while others are unique to unittest.mock. Remember that this lesson just covers some of the possible problems with mocking.
All the ones we’ve discussed here are related since they all lead to the same underlying issue. The claims made by the tests have no bearing on the outcomes. Each mockery has good intentions, but the mocks themselves aren’t.
not.
Changes to Object Interfaces and Misspellings
Definitions of classes and methods are always evolving. Any tests that depend on a Mock of an object may become obsolete if its interface is changed.
Forgetting that a test mocks a method and calling.assert not called() after the method’s renaming is an example of this. This modification has no effect on the truth value of.assert not called(). Although true, this claim is useless since the technique in question no longer exists.
Even though it may not seem like a big issue, relying on irrelevant tests and assuming they perform correctly might have catastrophic effects on your application.
One issue that only exists with Mock is that typos may invalidate tests. Remember that using a Mock’s members results in the creation of its interface. As a result, if you typo the name of an attribute, you’ll end up creating a new one.
An AssertionError will not be thrown in your test suite if you change.assert called() to.asert called(). The reason for this is because rather of assessing a real assertion, you have added a new method on the Python mock object called.asert called(). Intriguingly, assret is really a variant misspelling of the word assert. Accessing an attribute with the prefix assret (or assert) will cause Mock to throw an AttributeError.
These issues arise when mocking objects within the same codebase. Another issue arises when simulating things’ interactions with the outside world.
codebases.
Changes to External Dependencies
Let’s use the same thought experiment: your code makes a call to an outside API. In this situation, the API is the external dependence, and it may be updated without your knowledge or permission.
On the one hand, unit tests are used to verify individual modules of code. To further test your standalone components in a simulated environment, you may “mock” the code that initiates the request. There is, however, a possible catch with this.
Python mock objects will become obsolete if an external dependency’s interface is modified. If your fake objects are effective enough, your tests will pass even if the interface change is a breaking one, but your production code will crash.
Sadly, unittest.mock does not address this issue. Mocking people’s reliance on outside sources requires tact.
These three problems endanger the validity of your mock tests and might cost you time and money. Using unittest.mock, you’ll have access to a set of tools for simulating and testing
problems.
Avoiding Common Problems Using Specifications
As was previously discussed, you may break your tests by making changes to class or function definitions or by misspelling an attribute of a Python fake object.
Since Mock instantiates attributes and methods upon access, several issues arise. The solution to these problems is to forbid Mock from generating properties that deviate from the mocked object’s specifications.
The spec argument in a Mock’s configuration allows you to specify an object by name. The interface of the fake is specified by the spec argument, which might be a namelist or another object. The Mock library will throw an AttributeError if you try to access a property that is not part of the specification.
:
>>>
>>> from unittest.mock import Mock
>>> calendar = Mock(spec=['is_weekday', 'get_holidays'])
>>> calendar.is_weekday()
<Mock name='mock.is_weekday()' id='4569015856'>
>>> calendar.create_event()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
The.is weekday() and.get holidays() methods on Calendar have been declared here. A Fake is what you get back from calling.is weekday(). Mock throws an AttributeError if the non-conforming method.create event() is called.
While setting up the Mock, it makes no difference how you setup the specifications.
object:
Because you have calendar set up to utilise the same interface as the my calendar module, calendar now has access to the
>>>
>>> import my_calendar
>>> from unittest.mock import Mock
>>> calendar = Mock(spec=my_calendar)
>>> calendar.is_weekday()
<Mock name='mock.is_weekday()' id='4569435216'>
>>> calendar.create_event()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
.is weekday() function.
In addition, unittest.mock offers quick and easy ways to automatically define the interface of a Mock object.
create autospec is one method for enabling automatic specs.
:
>>>
>>> import my_calendar
>>> from unittest.mock import create_autospec
>>> calendar = create_autospec(my_calendar)
>>> calendar.is_weekday()
<MagicMock name='mock.is_weekday()' id='4579049424'>
>>> calendar.create_event()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'
Much like my calendar, calendar is a Mock instance with a similar interface. Patch(autospec )’s parameter accepts an input that does the same thing.
result:
>>>
>>> import my_calendar
>>> from unittest.mock import patch
>>> with patch('__main__.my_calendar', autospec=True) as calendar:
... calendar.is_weekday()
... calendar.create_event()
...
<MagicMock name='my_calendar.is_weekday()' id='4579094312'>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 582, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'create_event'