Skip to content

Instantly share code, notes, and snippets.

@taikedz
Last active December 5, 2023 11:12
Show Gist options
  • Save taikedz/2651e4504dc0932cebcc8c307bc51bce to your computer and use it in GitHub Desktop.
Save taikedz/2651e4504dc0932cebcc8c307bc51bce to your computer and use it in GitHub Desktop.
Mocker

Mocker

A versatile object mocking utility for unit testing.

For example, assuming a function that uses a connection utility:

def find_python_files_at(connection):
  try:
    status, stdout, stderr = connection.send_command("ls /my/path", shell=True)
    files = output.split("\n")
    return [F for F in files if F.endswith(".py")]
  except Exception:
    return []

A unit test can be written with a mock instances:

fake = Mocker()

def test_find_python_files():
  # the return value itself
  intedned_result = (0,  "file.py\nfile2.txt\nfile3.py", "")
  # Use all the parameters as they would be passed , and set the return value at the end
  fake.set__send_command("ls /my/path", shell=True, intended_result)

  # When this function calls `connection.send_command(...) , it will do so on the Mocker
  # Because the parameters match, the pre-seeded result will be returned
  res = find_python_files_at(fake)
  assert res == ["file.py", "file3.py"], res
  
  # Test the scenario where an exception is thrown
  # Instead of a value, pass a callable
  def _raiser():
    raise RuntimeError("test error")
  fake.set__send_command("ls /my/path", shell=True, _raiser)
  res = find_python_files_at(fake)
  assert res == [], f"Should have been empty, got: {res}"
from hashlib import md5
class Mocker:
""" Versatile object for mocking any class for testing.
Any `thing.my_function(<params>)` that is expected to return some result can be
pre-seeded with data by calling `thing.set_my_function(<params>, value)`
Substitute this object instead of a real class instance to mock all kinds of calls.
"""
def __init__(self):
self._reg = {}
def _hashify(self, data):
if isinstance(data, (str, int, float, bool)):
return str(data)
return md5(bytes(repr(data), "utf-8")).hexdigest()
def _make_identifier(self, sequence):
return '/'.join([self._hashify(k) for k in keys])
def _clear_registry(self):
self._reg.clear()
def _setit(self, *tokens):
keys, value = tokens[:-1], tokens[-1]
self._reg[self._make_identifier(keys)] = value
def _getit(self, *tokens):
result = self._reg.get(self._make_identifier(tokens))
if callable(result):
return result()
else:
return result
def __getattr__(self, __name: str):
if __name.startswith("set__"):
subname = __name[5:]
return lambda *params: self._setit(subname, *params)
else:
return lambda *params: self._getit(__name, *params)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment