Introduction

Unit testing is essential part of our daily struggles as a programmer. Therefore, if we want to be better in our work we have to advance in this approach.

In the article we are going to look at the mock module of Python's unittest library. In the end you will end up with levelled up unit testing skills.

What is mocking?

This is actually a test technique that allows you to isolate parts of your code and unit test them. You can easily fake function calls in your tests wherever needed.

In simple words, you can manipulate the return value and the side effects (e.g. raising an exception) of your functions however you want.

Yes, I was confused at the beginning too but let's dive into these inscrutable places with some examples!

Why do I have to use it?

I really hate when someone tells me 'Learn this, it's helpful!'. That's why I will list some cases where you can use mocking technique before start with the examples:

  • Calling functions in other functions
  • Working with datetime
  • Calling third-party apps

Calling functions

In order to keep with unit test practices we have to test single parts of our system independently. This means that if we have a function parent_func() that calls function child_func() we have to test them separately.

Usually, the result from child_func() is essential for parent_func() - the result of parent_func() depends on the result of child_func(). This is incongruous with the principals of unit testing. How can I still unit test then?

Simple example

Let's say we have the following function:

# examples.py
from random import randint


def get_number():
    number = randint(1, 100)

    if number % 2 == 0:
        return 'Even number: {}'.format(number)

    return 'Odd number: {}'.format(number)

Here we call the built-in randint() function from the random module. We are sure (hopefully) that python core developers have already tested this function so it would be а waste of time and resources to do it again!

Instead, we are going to test our get_number() function. But how can I test something that is generated randomly every time? The answer is simple - mock it!

# test_examples.py
from unittest import TestCase, mock, main

from examples import get_number


class GetNumberTests(TestCase):
    @mock.patch('examples.randint')
    def test_get_number_with_even_number(self, randint_mock):
        randint_mock.return_value = 42

        result = get_number()

        self.assertEqual('Even number: 42', result)

    @mock.patch('examples.randint')
    def test_get_number_with_odd_number(self, randint_mock):
        randint_mock.return_value = 69

        result = get_number()

        self.assertEqual('Odd number: 69', result)


if __name__ == '__main__':
    main()

And the output from python test_examples.py is:

..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

What does actually happen?

First of all, what does @mock.patch(examples.randint) mean? You actually tell Python "If you see the randint() method in the examples module - mock it's call!". That's how it very works.

Once you mock the call of a function it gives mock object argument to your test function but you have to pass it. You can name it whenever you want. If you don't pass it to the function a TypeError is raised - "test_func() expects 2 arguments but 1 was given".

OK, so now we have a randint_mock object and we have to do something with it! What we really want is to manipulate the return value of the randint() function.

This can be easily done by changing the return_value of your mock objects. By default it is None but you can literally set it to any Python object.

If you haven't done it yet check the official Python documentation for unittest.mocks.

Datetime example

Here is an example that you may find really handy one day.

# in examples.py
from datetime import datetime


def is_summer():
    month = datetime.now().month

    return month in [6, 7, 8]

Yes, I agree, this might not be the most useful function but the test we are going to write is. This is how it looks like:

class IsSummerTests(TestCase):
    @mock.patch('examples.datetime.now')  # <--- dafuq?
    def test_is_summer_in_august(self, now_mock):
        now_mock.return_value = datetime(year=2000, month=8, day=1)

        self.assertTrue(is_summer())

Let's run it:

E
======================================================================
ERROR: test_is_summer_in_august (__main__.IsSummerTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.5/unittest/mock.py", line 1149, in patched
    arg = patching.__enter__()
  File "/usr/lib/python3.5/unittest/mock.py", line 1312, in __enter__
    setattr(self.target, self.attribute, new_attr)
TypeError: can't set attributes of built-in/extension type 'datetime.datetime'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.5/unittest/mock.py", line 1170, in patched
    patching.__exit__(*exc_info)
  File "/usr/lib/python3.5/unittest/mock.py", line 1332, in __exit__
    setattr(self.target, self.attribute, self.temp_original)
TypeError: can't set attributes of built-in/extension type 'datetime.datetime'

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (errors=1)

Well, it doesn't work as expected. Why?

You may noticed that I'm trying to mock a function from built-in module. We did this in the previous example and saw it is not that hard. The problem in this example is that we are accessing now() via the datetime class of the datetime.module.

That's why this mock is impossible in fact. At least, datetime is not even part of our examples module.So it is impossible to test a function that uses the datetime module? Of course, the answer is no.

The right way of testing datetime

The solution is so simple. We can create our function that actually wraps/returns the datetime.now() object. Once we have it, we can mock it's call! This is how our example is going to look like:

# in examples.py
from datetime import datetime

def get_now():
    return datetime.now()


def is_summer():
    month = get_now().month

    return month in [6, 7, 8]

Now we can test is_summer():

from unittest import TestCase, mock, main
from datetime import datetime

from examples import is_summer

class IsSummerTests(TestCase):
    AUGUST = 8
    DECEMBER = 12

    @mock.patch('examples.get_now')
    def test_is_summer_in_august(self, now_mock):
        now_mock.return_value = datetime(year=2000, month=self.AUGUST, day=1)

        self.assertTrue(is_summer())

    @mock.patch('examples.get_now')
    def test_is_summer_in_december(self, now_mock):
        now_mock.return_value = datetime(year=2000, month=self.DECEMBER, day=1)

        self.assertFalse(is_summer())


if __name__ == '__main__':
    main()

Let's run it and see if it works this time:

....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK

Congratulations! You have just mocked the present...

Calling third-party apps

As we know, calling third-party apps in our projects is pretty common case. Check my previous article for more detailed analysis - Handle errors from third-party apps in Celery & Django.

That puts us in trouble: - If the third-party server has some issues our tests will fail and we cannot predict that - If we don't have internet connection our tests will fail and we cannot predict that

One of the worst things about programming is when you cannot predict your code! This is the time where our mocking skills come in place.

Simple Example

This is basically how calling a third party looks like:

from third_party import call_me_and_get_payload

def call_third_party():
    result = call_me_and_get_payload()

    return result

Test & Mock

Here is how out tests will look like:

from unittest import TestCase, mock, main

from examples import call_third_party

class CallThirdPartyTests(TestCase):
    @mock.patch('examples.call_me_and_get_payload')
    def test_call_third_party_returns_desired_payload(self, call_mock):
        call_mock.return_value = {}
        
        call_third_party()
        # Put your assertions here:
        # It's common to have some database transactions connected with the
        # third party payload.
        # You can assert them here

if __name__ == '__main__':
    main()

That's how you are not actually calling the third party so you can predict the behaviour of your tests and code.

Conclusion

We looked at some common cases where we can use unit test mocking. Eventhough we have mastered unit testing it is not enough to be sure our system works correctly.

You may also need some integration tests but having a lot unit tests is always a good way to ensure yourself that your code is covered and works the right way!