pytest Exceptions - Part II

We've already seen how to test exceptions with pytest in part I. However what's going to happen if we didn't fully implement our method yet or don't want to test on different os platforms, language versions etc.?

For these kind of tests we can use skip and xfail:

  • A skip means that you expect your test to pass only if some conditions are met,
  • An xfail means that you expect a test to fail for some reason.

Let's assume we got a dummy method which calls a third party API;

# utils.py
from typing import Optional

import requests

from .exceptions import ParametersError, PaymentGatewayError, UserPermissionError


def incomplete_business_logic_method(*args, **kwargs) -> Optional[str]:
    response = None
    try:
        response = expensive_calculation_method(*args, **kwargs)
    except APIGatewayError as exc:
        raise APIGatewayError() from exc
    except requests.exceptions.HTTPError as exc:
        if exc.response.status_code == 400:
            error = exc.response.json()
            code = error['code']
            message = error['message']

            if exc.code == 2001:
                raise ParametersError(exc.code, exc.message) from exc
            else:
                raise UserPermissionError(exc.code, exc.message) from exc

    return response

And exceptions module looks like this:

# exceptions.py
class APIGatewayError(Exception):
    pass


class PaymentGatewayError(APIGatewayError):
    def __init__(self, code, message):
        self.code = code
        self.message = message


class ParametersError(PaymentGatewayError):
    pass


class UserPermissionError(PaymentGatewayError):
    pass

xfail

You can use xfail marker to indicate that you expect a test to fail:

# tests/test_utils.py
import pytest

from .exceptions import APIGatewayError


@pytest.mark.xfail(raises=APIGatewayError)
def test_incomplete_business_logic_method(self):
    ...
    return incomplete_business_logic_method(*args, **kwargs)

If a test is only expected to fail under a certain condition — like in skipif,
you can pass that condition as the first parameter:

# tests/test_utils.py
import pytest


@pytest.mark.xfail(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_incomplete_business_logic_method(self):
    ...
    return incomplete_business_logic_method(*args, **kwargs)

skip

The simplest way to skip a test function is to mark it with the skip decorator which may be passed an optional reason:

# tests/test_utils.py
import pytest


@pytest.mark.skip(reason="not implement this yet")
def test_incomplete_business_logic_method(self):
    ...
    return incomplete_business_logic_method(*args, **kwargs)

skipif

If you wish to skip something conditionally then you can use skipif:

# tests/test_utils.py
import pytest


@pytest.mark.skiWe’ve already seen how to test exceptions with pytest in part I. However what’s going to happen if we didn’t fully implement our method yet or don’t want to test on different os platforms, language versions etc.? For these kind of tests we can use skip and xfail: A skip means that you expect your test to pass only if some conditions are met, An xfail means that you expect a test to fail for some reason.pif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_incomplete_business_logic_method(self):
    ...
    return incomplete_business_logic_method(*args, **kwargs)

Addition

You can show extra info on xfailed and skipped tests with -rxs:

$ pytest -rxs

Get more info on offical doc :skip and xfail.

All done!

22