6 Pytest

Last edited

Monkeypatching

Defintion

Monkeypatch: Replaces functions or objects at runtime to make the code easier to test.

Monkeypatches are brittle, if you find you’re overusing them, you likely need to make your code more generic.

Fixtures

Fixtures are helper functions.
You pass in their return values into the tests that call them.

@pytest.fixture
def file():
    return io.StringIO()

def test_xyz(file):
    ...

param to a fixture

Before

@pytest.mark.parametrize(
        "number",
        [1, 2, 3]
)
def test_double(number: int):
    assert double(number) == number * 2

After:

@pytest.fixture(params=[1, 2, 3])
def number(request):
    return request.param

def test_double(number: int):
    assert double(number) == number * 2

What’s the point? So we can re-use the paramaters (input) for other tests.
The trade off: you loose named inputs for larger tests, you would need to write a class if you had multiple in each tuple.

The takeway, use for the following (when there is reuse of inputs in either case):

  • inputs with one value per tuple
  • large inputs (class required)
@pytest.fixture(params=[
    ("Alice", 20),
    ("Bob", 30),
])
def user(request):
    return request.param

def test_name(user):
    # forced to unpack the tuple, rather than function sig doing it for us..
    name, age = user 
    assert name

Complex Testing

HTTP server for testing

import http.server
import json
import threading

@contextmanager
def serve(article):
    data = {"title": article.title, "extract": article.summary}
    body = json.dumps(data).encode()

    class Handler(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)

    with http.server.HTTPServer(("localhost", 0), Handler) as server:
        thread = threading.Thread(target=server.serve_forever, daemon=True)
        thread.start()
        yield f"http://localhost:{server.server_port}"
        server.shutdown()
        thread.join()

# test_xyz.py --------------
@pytest.fixture(scope="session")
def httpserver():
    ...