In many of my Python packages, I create classes that wrap the main functionality of my package. In main, it generally does nothing more than make a few API calls from these different classes. Python’s mock library can help a lot in testing this. I have separate test classes that test the other APIs. In main, I usually only want to test that I call those APIs correctly. Mock really helps with this.
This blog post assumes you have some knowledge about how the mock library works. If you do not have a basic understanding of it, you can view Python’s documentation of the mock library, or my previous post using the mock library.
A REST Example
A while back, I wrote an example Python application that provided a REST interface using MongoDB. You can read more about this application through these posts:
- Automated Testing with REST Example Part 1
- Automated Testing with REST Example Part 2
- Automated Testing with REST Example Part 3
Those posts talked more about REST, Docker, TDD, unit testing, and system testing. However, I did not dive into the use of the mock library in that project. The code for that project can be found on GitHub: restexample
Let’s look at my main function:
from flask import Flask
from flask_pymongo import PyMongo
from restexample.mongodb import MongoDB
from restexample.resources.add_api import add_resources
app = create_app()
app.config["MONGO_HOST"] = "192.168.1.2"
I first create a Flask application (a Python web server) in the create_app function. I then set a Mongo configuration variable. I add resources to the app (the resource in this case was a “Person” object that you can post to). I then set the Flask app into the PyMongo database, and finally I start the Flask app.
This main function doesn’t have a lot of logic in it. It only ties several different APIs together. So how can we test something like this? The mock library makes this fairly easy.
I create a new file with a test class in it called TestMain. The first test case only makes sure that the create_app function creates an object that is a Flask object. The second test case is more interesting…
from flask import Flask
from restexample.main import *
from unittest import mock, TestCase
def test_main_starts_app_on_port_5000(self, mock_pymongo, mock_db, mock_add_resources, mock_app):
I mock out 4 things in test_main_starts_app_on_port_5000:
- The create_app function
- The add_resources function
- The MongoDB class
- The PyMongo class
Notice that when you use multiple patch decorators, the decorator closest to the function (PyMongo in this example) is your first argument for your test function (mock_pymongo). Order is important.
I first call the main function. After that, I just do really simple checks for all these APIs:
- Make sure that the add_resources function was called.
- Make sure that set_mongodb was called.
- Make sure that the return value of the “create_app” function (a Flask object) has run called on it with certain arguments.
As stated earlier, I’m not really checking logic from my main function. Since it only ties together how I use several APIs, I am mocking out those APIs and just checking that they got called or were called with certain arguments. This gives me the ability to still test my main function, testing that it calls all these different APIs.
A word of caution: if you mock out these APIs incorrectly, you can potentially hide a bug in your code. Make sure that your mocks mimic the actual functionality of your program correctly, otherwise you can mask a problem in your source code.
Anytime your code does not do much more than hook together multiple APIs, the mock library can be used to verify that you exercise all those APIs correctly. Seeing what things get called and if they got called with certain arguments can be very useful. Something that would be quite difficult in other static languages (you might need an additional layer of abstraction to test it) is trivial using the mock library.