Testing with unittest.mock

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?

In short:

unittest.mock is 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.

With example:

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 🙂

Use cases

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: 1

# file at project/app1/foo_file

def foo(arg1, arg2):
    return arg1 + arg2

File: 2

# file at project/app2/bar_file
from project.app1.file1 import foo

def bar():
    try:
        return foo(1, 1)
    except NameError:
        return "Error"

File: 3

# 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

Conclusion

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 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s