Hello! Just 10 days back, there was a time when I tweet this.
While I always found it difficult, some people say writing mock test is super easy. I think it’s time for me to code more modular.
After a week of making the tweet, I set myself to read the official documentation with patience. The more I read, the more I started liking the tool. In the end, I understand people were right in saying that “mock tests are easy”. Below is a quick overview of what I could understand of writing mock test cases.
What is unittest.mock?
unittest.mockis a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.
mock objects here refers to kind of dummy objects. Whose behavior is under your control.
Let’s say you are making a web app in integration with another ticket managing web app, you are using its API in your code for a specific purpose, say buying tickets and gettings back the ticket id. So you write a code that sends a request to ticket managing app and gets the ticket id in response. But, here is the twist!
The ticket managing web app is kinda money minded and don’t want to entertain your request free of cost. So, you need to pay a little amount every time you make a request. Okay? Now you have written the code, you tested in 2-3 times and paid a small amount to the other app. That doesn’t matter much. But if you are a good developer, then you must have written automated test cases to test the behavior of your app. And every time you run the test suit, a couple of requests are made that costs you again, Another-Small-Amount.
In vigorous development, you run test suit countless times, and if every time it is going to charge you
a small amount it is well understood that its gonna give your wallet a really big deal.
Here comes, mock test. Using which you may kind of deactivate the functions of that web app API and assume their response. So you don’t need to send a real request to other application and this way you save your money 🙂
You write mock tests:
- while using 3rd party APIs. – As you want to test YOUR API, not their.
- while your code makes requests on a remote resource using the internet – As you might want to run your test cases even at places without internet.
- while sending request to an async tool – like celery beat, suppose the beat is set to 5 minutes, so it will run only after every 5 minutes, but its not a good idea to keep your test suit on hold till the next beat, so you just test calling the celery task not the actual running of that task.
- while you want to set explicitly the return value of a function – As you might want to test your feature for multiple return values of a function.
- while you want to explicitly raise an exception while a particular function gets called – As you might want to test the working of your code in a situation of getting encountered with an exception.
Example with mock.patch
There are lots of functions available in unittest.mock. For me, the patch is found to be the most useful. That’s why, I am showing just the patch function in this example that too very briefly, readers may explore more, as per their interest.
case 1: As a function decorator
# file at project/app1/foo_file def foo(arg1, arg2): return arg1 + arg2
# file at project/app2/bar_file from project.app1.file1 import foo def bar(): try: return foo(1, 1) except NameError: return "Error"
# file at project/app3/test_file from unittest import mock @mock.patch('project.app2.bar_file.foo') def test_bar(mock_foo): # Here foo() is now mocked in bar_file, and this mocked function # is passed to kwarg: mock_foo for further references. bar() # Calling the function under test # testing if mock function was called assert mock_foo.assert_called_with(1, 1) is None assert mock_foo.assert_called_once_with(1, 1) is None # manipulating the return value of mock function mock_foo.return_value = 5 assert bar() == 5 # manipulating the mock function to raise exception where it gets called mock_foo.side_effect = NameError('reason') assert bar() == "Error"
NOTE: Where to patch?
We need to patch the function where it is gettings used, not where it is defined. In the example above
foo is defined in
foo_file but used in
bar_file thus we mocked the
foo function in
bar_file(see argument passed to @patch()).
case 2: As a context manager
In the example above, we patched a function
foo in a complete function, but if we don’t want that instead, we just to mock a function for a limited scope in a test function. Here it is how to.
File: 3 (File: 1 and File: 2 remains same)
# file at project/app3/test_file from unittest import mock def test_bar(): with mock.patch('project.app2.bar_file.foo') as mock_foo: # Here foo() is now mocked in bar_file, and this mocked function # can now be referenced using mock_foo. mock_foo.return_value = 5 assert bar() == 5 # Inside 'with' scope: mocked behavior present assert bar() == 2 # Outside 'with' scope mocked behavior absent
Explore more unittest.mock
I see unittest.mock as a really useful tool for all the use cases listed above. I hope you don’t find mock testing difficult, but if you do, then I seriously suggest to read the official docs, they are just lovely and shows the power of documentation!
Thanks for reading! See you in the next post 🙂