How to Mock Well in Tests


Traps and Pitfalls When Using Mock and Pytest

Grab the slides:

Do you write tests?

What is Unit test


Test function

Check if matches expected output

✅ or ❌



pytest is a framework that makes building simple and scalable tests easy.

Perfect for unit test, also have other features via fixtures: patching, mocking, parameterize...

pip install -U pytest

pytest --version

Test functions

import pytest

def serve_beer(age):
  if (age is None) or (age<18):
    return "No beer"
    return "Have beer"

def test_serve_beer_legal():
  adult = 25
  assert serve_beer(adult) == "Have beer"

def test_serve_beer_illegal():
  child = 10
  assert serve_beer(child) == "No beer"

Test functions

To run it, in the ternimal:

pytest <>::<test_your_func>

Let's see it in action!!!

Expected Exceptions

import pytest

def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0
import pytest

def myfunc():
    raise ValueError("Exception 123 raised")

def test_match():
    with pytest.raises(ValueError, match=r".* 123 .*"):

there is also another Way (xfail)

Making a "unit" for test is not always easy

Monkey patching in Pytest

Monkey patching is replacing a function/method/class by another at runtime, for testing purpses, fixing a bug or otherwise changing behaviour.

It is useful when your test involve some external funtions (e.g. getting a result from the backend with your API call)

monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, raising=True)
from pathlib import Path

def getssh():
    """Simple function to return expanded homedir ssh path."""
    return Path.home() / ".ssh"

def test_getssh(monkeypatch):
    # mocked return function to replace Path.home
    # always return '/abc'
    def mockreturn():
        return Path("/abc")

    # Application of the monkeypatch to replace Path.home
    # with the behavior of mockreturn defined above.
    monkeypatch.setattr(Path, "home", mockreturn)

    # Calling getssh() will use mockreturn in place of Path.home
    # for this test with the monkeypatch.
    x = getssh()
    assert x == Path("/abc/.ssh")

Note that in the example, you have to write your own mockreturn function. With a more complex object (requests library objs), it could be quite complicated to create a mock object yourself. 


Using Mock


The mock library gives you an object you can use to monkey-patch. The mock object from the mock library also gives you excellent features out of the box that helps you test that the mock behaves a certain way.

from unittest import mock
import requests
from requests.exceptions import HTTPError

def test_google_query(self, mock_get):
  """test google query method"""
  mock_resp = self._mock_response(content="ELEPHANTS")
  mock_get.return_value = mock_resp

  result = google_query('elephants')
  self.assertEqual(result, 'ELEPHANTS')

Mock.patch Decorators


Note: With patch() it matters that you patch objects in the namespace where they are looked up. This is normally straightforward, but for a quick guide read where to patch.


Take away

  1. Invest in testing
  2. Find the right tool for testing
  3. Share your experience in testing and help others