pytest-mock Basics
━━━━━━━━━━━━━━━━━━

Last Thursday I learned about at a local python meetup. The presenter showed how he uses for his work, and it was kinda eye opening. I knew what mocking was,...

Date: March 14, 2022

Last Thursday I learned about [38;2;189;147;249mpytest-mock[0m at a local python meetup. The presenter showed how he uses [38;2;189;147;249mpytest-mock[0m for his work, and it was kinda eye opening. I knew what mocking was, but I had not seen it in this context.

[1m[38;2;189;147;249mDiscovery[0m
[38;2;68;71;90m─────────[0m

Watching him use [38;2;189;147;249mpytest-mock[0m I realized that mocking was not as hard as I had made it out to be. You can install [38;2;189;147;249mpytest-mock[0m, use the mocker fixture, and patch objects methods with what you want them to be.

[1m[38;2;189;147;249minstall[0m
[38;2;68;71;90m───────[0m

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

[38;2;248;248;242m[code][0m
  python -m pip install pytest-mock

[1m[38;2;189;147;249mWhat I actually did[0m
[38;2;68;71;90m───────────────────[0m

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.

[38;2;248;248;242m[code][0m
  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

[1m[38;2;189;147;249mcreate a tests/test_me.py[0m
[38;2;68;71;90m─────────────────────────[0m

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 [38;2;189;147;249mstatus_code[0m of the request.

I want to mock out [38;2;189;147;249mrequests[0m to ensure that GoGetter can handle both [38;2;189;147;249m200[0m (http success) and [38;2;189;147;249m404[0m (http not found) status codes.

[38;2;248;248;242m[code][0m
  # 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
