86

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?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
Alex Tereshenkov
  • 3,340
  • 8
  • 36
  • 61
  • 1
    http://stackoverflow.com/questions/53513/best-way-to-check-if-a-list-is-empty May be of some help. – CollinD Oct 19 '15 at 14:05
  • 1
    Good link! I know how to test whether an iterable is empty though; was looking for specifically unittest assert methods... Surprised there is no assertEmpty/assertNotEmpty methods - imho it would improve readability a lot, instead of having assertTrue and assertFalse all around the code... – Alex Tereshenkov Oct 19 '15 at 14:14
  • 2
    Agreed, just figured that `assertTrue` is maybe a tad cleaner. I think my personal preference (and maybe the most semantically meaningful option) would be to assert len != 0. I'd agree that an assertEmpty/assertNotEmpty method has a place in the unittest framework. – CollinD Oct 19 '15 at 14:20
  • 4
    Note that "iterable" is much larger than "lists or dictionaries", and there's no way to tell if an iterable in general is empty without actually iterating it. – DSM Oct 19 '15 at 14:31

7 Answers7

127

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

gplayer
  • 1,741
  • 1
  • 14
  • 15
10

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)
Josh J
  • 6,813
  • 3
  • 25
  • 47
10

"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)
winklerrr
  • 13,026
  • 8
  • 71
  • 88
  • 1
    isn't this susceptible to misleading-ness when e.g. a scalar or object is passed by accident? – Merk Nov 27 '18 at 20:48
  • @Merk yes, you are right about that. But if you use these helper functions for private reasons only, it should be fine because you know what the functions do. If this code is meant to be in a little project with other devs, you may want to add some doc string explaining it's safe usage or just rename the function properly (e.g. `assert_list_empty(self, a_list)`). Or if you plan to add these functions to your third-party test library, just add some code to make sure you handle all the possible inputs (by adding `if ... elif ... else ...` and instance checks) – winklerrr Nov 28 '18 at 09:17
1

Maybe:

self.assertRaises(StopIteration, next(iterable_object))
Eugene Soldatov
  • 9,755
  • 2
  • 35
  • 43
0

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 )
Rick Graves
  • 517
  • 5
  • 11
0

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.

jfaccioni
  • 7,099
  • 1
  • 9
  • 25
0

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')
David Gard
  • 11,225
  • 36
  • 115
  • 227