Cheuk Ting Ho
Developer advocate / Data Scientist - support open-source and building the community.
Grab the slides:
slides.com/cheukting_ho/how-to-mock-well-in-tests
Input
Test function
Check if matches expected output
Function
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
import pytest
def serve_beer(age):
if (age is None) or (age<18):
return "No beer"
else:
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"
To run it, in the ternimal:
pytest <you_test.py>::<test_your_func>
Let's see it in action!!!
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 .*"):
myfunc()
there is also another Way (xfail)
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)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
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.
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.
https://docs.python.org/3/library/unittest.mock.html#module-unittest.mock
patch(), patch.object() and patch.dict()
https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators
from unittest import mock
import requests
from requests.exceptions import HTTPError
@mock.patch('requests.get')
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')
self.assertTrue(mock_resp.raise_for_status.called)
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.
https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators
By Cheuk Ting Ho
Developer advocate / Data Scientist - support open-source and building the community.