=======
== G ==
=======

Unit testing basics with Python

Libs used: pytest and mock

The mock lib provides a function called patch, used as a decorator to mock the things we are going to import in our to-be-tested function. In this example, CouchDB is being used and we are going to our application’s save method. Our function imports CouchDB from cloudant, but we don’t want to use the real CouchDB object. The reason is that we want to make sure all the results are coming from our program, not some third-party library.

Example of our dummy program:

from cloudant import CouchDB

class Connector:
    def __init__(self):
        self.couchdb = CouchDB(...)

    def save(document):
        couchdb.bulk_docs([document])

To test the save method, the CouchDB class should be mocked, which is the class that is imported inside the Connector module. The patch decorator is useful for that. I’m assuming the Connector lives in a dummy path: databases.couchdb.

from mock import patch

@patch('database.couchdb.CouchDB')
def test_save(mocked_couchdb):
   ...

With that, the return value of the CouchDB class objects are all Mock() objects. So everytime the self.couchdb = CouchDB(...) is executed, self.couchdb receives a Mock() object. And, of course, we can fiddle with the return value, it can be basically whatever we want. We could change it using a return_value parameter in the path or setting mocked_couchdb.return_value = ....

We can also use the side_effect parameter, which is basically a list containing the sequence of return values that are attributed to the object as the test cases run. And, lastly, speaking of test cases, to avoid calling the save method several times with different documents to test every possible case (and, frankly, turning the test basically unreadable), we can use the parametrize decorator.

It has two parameters, a tuple containing the names of the test cases and a list containing tuples with the test cases. Going on with the database save example, we can store the bulk_docs (it’s a real method from cloudant’s CouchDB) responses in a variable and then assert that the response is actually what it’s supposed to be, that’s what tests are for, right? So:

import pytest
from mock import patch

@patch('database.couchdb.CouchDB')
@pytest.mark.parametrize(
    ('document', 'expected_response'), [({'_id': '1'}, dummy_return), (...)])
def test_save(mocked_couchdb, document, expected_response):
    connector = Connector()
    response = connector.save(document)
    assert response == expected_return

Breaking it down a little bit: the tuple ('document', 'expected_response') represents what we are providing as test cases, and in the cases array, in the first tuple: ({'_id': '1'}, dummy_return), {'_id': '1'} is the document, while dummy_return is the expected return.

We can add as many test cases as we want in the array of tests, e.g.: ({'_id': '2'}, dummy_return2), ({'_id': '3'}, False), etc.

The test will run for every case we have defined in the parametrize. Therefore, we asserted that given an input, it returned the correct response. And this covers the basic stuff of testing with pytest and mock, how to use the return values and the side effects. It should be enough for most applications.

Don’t forget to check the documentations as well. There are plenty of other things there.