Python testing - Introduction to mocking
What is mocking?¶
Let me try and explain what mocking is and when you need it, with an example —
Suppose you have a web app where users can sign up. When a user signs up, your app automatically sends them an email asking them to verify their email address. The workflow would look like this:
- User sees the page
- User submits the sign up form
- Check if credentials are valid
- Create the user if valid OR show error if invalid
- Send email if user is created
So, your app probably has a few functions (or classes) for doing specific tasks such as validating, saving to database, sending email, and a main sign up function to render the form. etc.
Let's say the function that handles sign ups, and the function that sends email look roughly like this:
def sign_up(username, password):
"""The sign up function, called when a user submits form"""
# check if user credentials are valid
# save user
# send email
send_verification_mail(emal=user.email)
def send_verification_mail(email):
# compose a mail and send
return True
Say you want to test the sign_up
function to see if the sign up workflow behaves
as expected, but you don't want to trigger sending emails. You might be creating
users many times during the tests. So, sending emails every time can be
a bad idea because it can slow down your tests. Or if the mail server returns
any error, your tests would fail (of course, you can handle those errors, but
for the sake of this example, bear with me).
Since, the sign_up
function calls the send_verification_mail
function
from within, it's guaranteed that if you test sign_up
function,
you will trigger sending email.
This gives rise to the question: How can I test sign_up
function without
calling send_verification_mail
function?
This is where mocking comes in. Mocking means you can create a fake function
to replace send_verifiation_mail
in your tests. That's it.
A Simple example¶
Note:
If you're using Python 2.7 or any version < 3.3, you'll need to install the
mock
package:
$ pip install mock
For Python >= 3.3, the mock
is included in the standard library and
can be imported as unittest.mock
.
First create a file called main.py
. Our code will live in this.
# main.py
import time
def long_process():
# to emulate a long running
# process, we'll use time.sleep
time.sleep(5)
return True
def call_long_process():
long_process()
return True
In main.py
, we have a function called long_process()
which sleeps for 5
seconds
and returns True
.
There's another function in main.py
called call_long_process()
. It just calls
the long_process_function()
and returns True
.
Now, let's write some tests for the above code in a file called tests.py
:
# tests.py
from unittest import TestCase
from mock import patch # for Python >= 3.3 use unittest.mock
from main import long_process, call_long_process
class MainTests(TestCase):
"""Tests for `long_process`"""
def test_long_process(self):
# test if `long_process` returns true
self.assertTrue(long_process())
def test_call_long_process(self):
# test if `call_long_process`
# actually calls `long_process`
self.assertTrue(call_long_process())
In tests.py
, we are testing that long_process()
function works correctly and
it should return True
.
We're also testing that call_long_process()
function calls the long_process()
function.
Run the tests:
$ python -m unittest tests
# Output
..
----------------------------------------------------------------------
Ran 2 test in 10.004s
OK
Our test passed but it took a long time. In our tests, the long_process()
function
is being called twice - once in test_long_process
and again in test_call_long_process
.
In the second test, we only want to test if call_long_process()
actually calls
long_process()
. We don't care it long_process()
works properly or not, because
we have tested that in the first test. So, there's no point in waiting 5 more seconds
for the process to finish.
Let's patch the long_process()
function in our second test.
# tests.py
class MainTests(TestCase):
# ...
# same code as above ...
def test_call_long_process(self):
with patch('main.long_process', return_value=True) as mock_process:
# test the return value of `call_long_process`
self.assertTrue(call_long_process())
# test if `call_long_process`
# issues a call to `long_process`
mock_process.assert_called_once()
Above, we've patched the long_process()
function in the second test. We've called
the mock object mock_process
. We test that the return value of call_long_process()
is True
, and we also test that it actually calls long_process()
. But since,
we've created a mock object for it, we have to test if mock_process
is called or
not. That would tell us if call_long_process()
issued a call, or not.
Let's run our tests:
$ python -m unittest tests
# Output
..
----------------------------------------------------------------------
Ran 2 test in 5.003s
OK
It worked! This time it took only 5 seconds to run all the tests. This means that
the long_process()
was actually called only once.
To execute our tests even faster, we can keep the tests for long_process()
in a
separate file and run them only when we have to.
That's it¶
In this post, I covered what mocking is and when you need it. I've also shown a very basic example of getting started with mocking.
In the next post, I'll write about how you can mock HTTP requests made using the
requests
library.