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:

  1. User sees the page
  2. User submits the sign up form
  3. Check if credentials are valid
  4. Create the user if valid OR show error if invalid
  5. 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.