How to assert that an iterable is not empty on Unittest?

After submitting queries to a service, I get a dictionary or a list back and I want to make sure it's not empty. I using Python 2.7.

I am surprised of not having any assertEmpty method for the unittest.TestCase class instance.

The existing alternatives just don't look right:

self.assertTrue(bool(d))
self.assertNotEqual(d,{})
self.assertGreater(len(d),0)

Is this kind of a missing method in the Python unittest framework? If yes, what would be the most pythonic way to assert that an iterable is not empty?

83041 次浏览

Maybe:

self.assertRaises(StopIteration, next(iterable_object))

Empty lists/dicts evaluate to False, so self.assertTrue(d) gets the job done.

Depends exactly what you are looking for.

If you want to make sure the object is an iterable and it is not empty:

# TypeError: object of type 'NoneType' has no len()
# if my_iterable is None
self.assertTrue(len(my_iterable))

If it is OK for the object being tested to be None:

self.assertTrue(my_maybe_iterable)

"Falsy" values in Python

A falsy (sometimes written falsey) value is a value that is considered false when encountered in a Boolean context.

According to the official doc, the following built-in types evaluate to false:

  • constants defined to be false: None and False.
  • zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • empty sequences and collections: '', (), [], {}, set(), range(0)

Therefore, it's possible to check for

(The official doc has a full list of all available assert methods.)

Clean Code

All those assertTrue() and assertFalse() calls are kind of misleading as we wanted to check for emptiness and one needs to know which types evaluate to false to properly understand what's happening in the test.

So, for the sake of clean code and for better readability, we can simply define our own assertEmpty() and assertNotEmpty() methods like so:

def assertEmpty(self, obj):
self.assertFalse(obj)


def assertNotEmpty(self, obj):
self.assertTrue(obj)

All the credit for this goes to winklerrr, I am just extending his idea: have importable mixins for when you need assertEmpty or assertNotEmpty:

class AssertEmptyMixin( object ):
def assertEmpty(self, obj):
self.assertFalse(obj)


class AssertNotEmptyMixin( object ):
def assertNotEmpty(self, obj):
self.assertTrue(obj)

Caveat, mixins should go on the left:

class MyThoroughTests( AssertNotEmptyMixin, TestCase ):
def test_my_code( self ):
...
self.assertNotEmpty( something )

Based on @winklerr's answer and @Merk's comment, I extended the idea for checking whether the given object is a Container in the first place.

from typing import Container




def assertContainerEmpty(self, obj: Container) -> None:
"""Asserts whether the given object is an empty container."""
self.assertIsInstance(obj, Container)
self.assertFalse(obj)


def assertContainerNotEmpty(self, obj: Container) -> None:
"""Asserts whether the given object is a non-empty container."""
self.assertIsInstance(obj, Container)
self.assertTrue(obj)

This means that assertEmpty and assertNotEmpty will always fail if the given object is e.g. a float, or an instance of an user-defined class - no matter if it would properly evaluate to True/False.

A slightly different answer to those already proposed... If specific named assertions are absolutely required, you could subclass TestCase and add methods for new assertions there.

from pathlib import Path
from typing import Container
from unittest import TestCase


class BaseTestCase(TestCase):
def assertIsFile(self, path: str, msg: str=None) -> None:
default_msg = 'File does not exist: {0}'.format(path)
msg = msg if msg is not None else default_msg
if not Path(path).resolve().is_file():
raise AssertionError(msg)
    

def assertIsEmpty(self, obj: Container, msg: str=None) -> None:
default_msg = '{0} is not empty.'.format(obj)
msg = msg if msg is not None else default_msg
self.assertIsInstance(obj, Container, '{0} is not a container.'.format(obj))
if len(obj) > 0:
raise AssertionError(msg)
    

def assertIsNotEmpty(self, obj: Container, msg: str=None) -> None:
default_msg = '{0} is empty.'.format(obj)
msg = msg if msg is not None else default_msg
self.assertIsInstance(obj, Container, '{0} is not a container.'.format(obj))
if obj is None or len(obj) == 0:
raise AssertionError(msg)

And then subclass the new BaseTestCase class to use the new assertion methods.

class TestApplicationLoadBalancer(_BaseTestCase):


def setUp(self) -> None:


# These assertions will fail.
self.assertIsFile('does-not-exist.txt')
self.assertIsEmpty(['asdf'])
self.assertIsNotEmpty([])

Just like the built-in unittest assertions, you can pass an error message to these if desired.

class TestApplicationLoadBalancer(_BaseTestCase):


def setUp(self) -> None:


# These assertions will fail.
self.assertIsFile('does-not-exist.txt', 'Foo')
self.assertIsEmpty(['asdf'], 'Bar')
self.assertIsNotEmpty([], 'Baz')