Skip to content

Unit testing with pytest

Unit test your OpenFaaS Python functions using pytest and tox. Tests run without deploying, giving fast feedback during development.

Use-cases:

  • Validating request parsing and response formatting
  • Testing business logic in isolation
  • Running tests in CI before deploying

This example shows a small calculator function that validates input with pydantic, and a test suite that exercises it by mocking the event object.

Overview

handler.py:

import json
from pydantic import BaseModel, validator

class CalcRequest(BaseModel):
    op: str
    var1: float
    var2: float

    @validator("op")
    def op_must_be_valid(cls, v):
        if v not in ("+", "-", "*", "/"):
            raise ValueError(f"unsupported operation: {v}")
        return v

def handle(event, context):
    try:
        req = CalcRequest(**json.loads(event.body))
    except Exception as e:
        return {
            "statusCode": 400,
            "body": {"error": str(e)}
        }

    if req.op == "+":
        result = req.var1 + req.var2
    elif req.op == "-":
        result = req.var1 - req.var2
    elif req.op == "*":
        result = req.var1 * req.var2
    elif req.op == "/":
        if req.var2 == 0:
            return {
                "statusCode": 400,
                "body": {"error": "division by zero"}
            }
        result = req.var1 / req.var2

    return {
        "statusCode": 200,
        "body": {"value": result}
    }

handler_test.py:

from . import handler as h
from types import SimpleNamespace

def make_event(body, method="POST"):
    return SimpleNamespace(
        body=body,
        headers={},
        path="/",
        method=method,
    )

class TestCalculator:
    def test_addition(self):
        event = make_event('{"op": "+", "var1": 1.0, "var2": 2.0}')
        resp = h.handle(event, {})
        assert resp["statusCode"] == 200
        assert resp["body"]["value"] == 3.0

    def test_subtraction(self):
        event = make_event('{"op": "-", "var1": 5.0, "var2": 3.0}')
        resp = h.handle(event, {})
        assert resp["statusCode"] == 200
        assert resp["body"]["value"] == 2.0

    def test_division_by_zero(self):
        event = make_event('{"op": "/", "var1": 1.0, "var2": 0}')
        resp = h.handle(event, {})
        assert resp["statusCode"] == 400

    def test_invalid_operator(self):
        event = make_event('{"op": "^", "var1": 1.0, "var2": 2.0}')
        resp = h.handle(event, {})
        assert resp["statusCode"] == 400

requirements.txt:

pydantic

Step-by-step walkthrough

Create the function

Pull the template and scaffold a new function:

faas-cli template store pull python3-http
faas-cli new --lang python3-http calc \
  --prefix ttl.sh/openfaas-examples

Update calc/handler.py and calc/requirements.txt with the code from the overview above.

Add a test file

The python3-http template scaffolds a tox.ini and a skeleton handler_test.py inside the function's handler directory. Replace the contents of calc/handler_test.py with the test code from the overview.

The tox.ini is preconfigured to discover and run pytest tests. You can inspect it with:

cat calc/tox.ini

Build and run the tests

Testing is disabled by default in the python3-http template. Enable it by passing --build-arg TEST_ENABLED=true to faas-cli build:

faas-cli build \
  --filter calc \
  --tag digest \
  --build-arg TEST_ENABLED=true

During the build you should see output similar to:

handler_test.py ....                                                     [100%]

========================= 4 passed in 0.14s =========================

To enable testing permanently, add TEST_ENABLED to your stack.yml:

functions:
  calc:
    lang: python3-http
    handler: ./calc
    image: ttl.sh/openfaas-examples/calc:latest
    build_args:
      TEST_ENABLED: "true"

Deploy and invoke

Build, push and deploy the function:

faas-cli up \
  --filter calc \
  --tag digest

Invoke the calculator:

curl -s http://127.0.0.1:8080/function/calc \
  -d '{"op": "+", "var1": 1, "var2": 2}'