How to write assert statements to test for ValueError

Viewed 187

I wrote the following for a Enum class

class AbstractBaseQuotation(object):
    @total_ordering
    class STATE(namedtuple("State", "human_readable path order_in_path description"), Enum):
        DRAFT = "Draft", "happy", 1, "Anything that is not sent to vendor"
        PENDING_VENDOR = (
            "Pending Vendor",
            "happy",
            2,
            "Anything that is in vendor inbox",
        )
        
        CANCELLED = (
            "Cancelled",
            "cancel",
            None,
            "Any Order past the Draft stage can be cancelled",
        )

        @property
        def db_value(self):
            """
            Opinionated decision to use the member name as the database value to store
            """
            return self.name

        def _is_cancelled(self):
            return self == AbstractBaseQuotation.STATE.CANCELLED

        def __lt__(self, other):
            if (
                self.__class__ is AbstractBaseQuotation.STATE.DRAFT.__class__
                and self._is_cancelled()
            ):
                raise ValueError("Cannot compare CANCELLED with other states")
            if (
                other.__class__ is AbstractBaseQuotation.STATE.DRAFT.__class__
                and other._is_cancelled()
            ):
                raise ValueError("Cannot compare CANCELLED with other states")
            if self.__class__ is other.__class__:
                return self.order_in_path < other.order_in_path
            return NotImplemented

This is my unit test

   with self.assertRaises(ValueError) as raised:

       AbstractBaseQuotation.STATE.CANCELLED > AbstractBaseQuotation.STATE.DRAFT

I know what assertRaises expects a callable. but I am not sure how to test

I have also tried this:

def test_compare_across_states(self):
    with self.assertRaises(ValueError) as raised:

        def compare():
            return AbstractBaseQuotation.STATE.CANCELLED > AbstractBaseQuotation.STATE.DRAFT

        compare()

Both times I get AssertionError: ValueError not raised

How do I test for ValueError in that case?

2 Answers

It's hard to reason about your problem without giving us a bit more code. I assumed how it might look like and tried myself. Tests pass, so I can't replicate what's happening to you.

from enum import IntEnum, Enum
import pytest


class KimEnum(IntEnum):
    CANCELLED = 1
    OTHER = 2

    def _is_cancelled(self):
        return self == self.CANCELLED

    def __lt__(self, other):
        if self._is_cancelled():
            raise ValueError
        raise RuntimeError("Should not reach this")


class EnumHolder:
    STATE = KimEnum


def test_kim_enum():
    assert KimEnum.CANCELLED == 1

    with pytest.raises(ValueError):
        _ = KimEnum.CANCELLED < 2


def test_enum_holder():
    assert EnumHolder.STATE.CANCELLED == 1

    with pytest.raises(ValueError):
        _ = EnumHolder.STATE.CANCELLED < 2

Thanks to @kszl comment, I think I cracked it

   def _flag_for_cancelled(self, other):
        if (
            self.__class__ is AbstractBaseQuotation.STATE.DRAFT.__class__
            and self._is_cancelled()
        ):
            raise ValueError("Cannot compare CANCELLED with other states")
        if (
            other.__class__ is AbstractBaseQuotation.STATE.DRAFT.__class__
            and other._is_cancelled()
        ):
            raise ValueError("Cannot compare CANCELLED with other states")

    def __lt__(self, other):
        self._flag_for_cancelled(other)
        if self.__class__ is not other.__class__:
            return NotImplemented
        return self.order_in_path < other.order_in_path

    def __gt__(self, other):
        self._flag_for_cancelled(other)
        if self.__class__ is not other.__class__:
            return NotImplemented
        return not self.__lt__(other)

    def __le__(self, other):
        self._flag_for_cancelled(other)
        if self.__class__ is not other.__class__:
            return NotImplemented
        return not self.__gt__(other)

    def __ge__(self, other):
        self._flag_for_cancelled(other)
        if self.__class__ is not other.__class__:
            return NotImplemented
        return not self.__lt__(other)

This is the unit test. Apparently NotImplemented will trigger TypeError instead

def test_compare_across_states(self):
    # this is for greater than
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.CANCELLED > AbstractBaseQuotation.STATE.DRAFT
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.DRAFT > AbstractBaseQuotation.STATE.CANCELLED
    with self.assertRaises(TypeError):
        _ = AbstractBaseQuotation.STATE.DRAFT > 7

    # ge
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.CANCELLED >= AbstractBaseQuotation.STATE.DRAFT
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.DRAFT >= AbstractBaseQuotation.STATE.CANCELLED
    with self.assertRaises(TypeError):
        AbstractBaseQuotation.STATE.DRAFT >= 7

    # this is for less than
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.CANCELLED < AbstractBaseQuotation.STATE.DRAFT
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.DRAFT < AbstractBaseQuotation.STATE.CANCELLED
    with self.assertRaises(TypeError):
        AbstractBaseQuotation.STATE.DRAFT < 7

    # le
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.CANCELLED <= AbstractBaseQuotation.STATE.DRAFT
    with self.assertRaises(ValueError):
        _ = AbstractBaseQuotation.STATE.DRAFT <= AbstractBaseQuotation.STATE.CANCELLED
    with self.assertRaises(TypeError):
        AbstractBaseQuotation.STATE.DRAFT <= 7
Related