Last Thursday I learned about pytest-mock at a local python meetup. The presenter showed how he uses pytest-mock for his work, and it was kinda eye opening. I knew what mocking was, but I had not seen it in this context.

Discovery

Watching him use pytest-mock I realized that mocking was not as hard as I had made it out to be. You can install pytest-mock, use the mocker fixture, and patch objects methods with what you want them to be.

install

pytest-mock is out on pypi and can be installed with pip.

python -m pip install pytest-mock

What I actually did

Sometimes I fall victim to making these posts nice and easy to follow. It takes more steps than just pip install, you need a place to practice in a nice sandbox. Here is how I make my sandboxes.

mkdir ~/git/learn-pytest-mock
cd ~/git/learn-pytest-mock
# well actually open a new tmux session there
echo pytest-mock > requirements.txt

I copied in my .envrc, and ran direnv allow, which actually just made me a virtual env as follows #

python3 -m venv .venv --prompt $(basename $PWD) source .venv/bin/activate

now install pytest-mock #

pip install -r requirements.txt

make some tests to mock #

mkdir tests nvim tests/test_me.py

create a tests/test_me.py

I just wanted to do something that was worth mocking, the first thing that came to mind was to do something that made a network call. Here I made a method that uses requests to go get the content on my homepage, but changes it's return behavior based on the status_code of the request.

I want to mock out requests to ensure that GoGetter can handle both 200 (http success) and 404 (http not found) status codes.

# tests/test_me.py
import requests

class GoGetter: """ The thing I am testing, this is usually imported into the test file, but defined here for simplicity. """ def get(self): """ Get the content of https://waylonwalker.com and return it as a string if successfull, or False if it's not found. """ r = requests.get("https://waylonwalker.com") if r.status_code == 200: return r.content if r.status_code == 404: return False

class DummyRequester: def init(self, content, status_code): """ mock out content and status_code """

    self.content = content
    self.status_code = status_code

def __call__(self, url):
    """
    The way I set this up GoGetter is going to call an instance of this
    class, so the easiest way to make it work was to implement __call__.
    """
    self.url = url
    return self

def test_success_get(mocker): """ Show that the GoGetter can handle successful calls. """ go_getter = GoGetter()

# Use the mocker fixture to change how requests.get works while inside of test_success_get
mocker.patch.object(requests, "get", DummyRequester("waylonwalker", 200))
assert "waylon" in go_getter.get()

def test_failed_get(mocker): """ Show that the GoGetter can handle failed calls. """ go_getter = GoGetter()

# Use the mocker fixture to change how requests.get works while inside of test_failed_get
mocker.patch.object(requests, "get", DummyRequester("waylonwalker", 404))
assert go_getter.get() is False