Expecting Exceptions

Exceptions may be raised in a few places in the Tasks API. Let’s take a quick peek at the functions found in tasks/api.py:

 def​ add(task): ​# type: (Task) -> int
 def​ get(task_id): ​# type: (int) -> Task
 def​ list_tasks(owner=None): ​# type: (str|None) -> list of Task
 def​ count(): ​# type: (None) -> int
 def​ update(task_id, task): ​# type: (int, Task) -> None
 def​ delete(task_id): ​# type: (int) -> None
 def​ delete_all(): ​# type: () -> None
 def​ unique_id(): ​# type: () -> int
 def​ start_tasks_db(db_path, db_type): ​# type: (str, str) -> None
 def​ stop_tasks_db(): ​# type: () -> None

There’s an agreement between the CLI code in cli.py and the API code in api.py as to what types will be sent to the API functions. These API calls are a place where I’d expect exceptions to be raised if the type is wrong.

To make sure these functions raise exceptions if called incorrectly, let’s use the wrong type in a test function to intentionally cause TypeError exceptions, and use with pytest.raises(<expected exception>), like this:

ch2/tasks_proj/tests/func/test_api_exceptions.py
 import​ pytest
 import​ tasks
 
 
 def​ test_add_raises():
 """add() should raise an exception with wrong type param."""
 with​ pytest.raises(TypeError):
  tasks.add(task=​'not a Task object'​)

In test_add_raises(), the with pytest.raises(TypeError): statement says that whatever is in the next block of code should raise a TypeError exception. If no exception is raised, the test fails. If the test raises a different exception, it fails.

We just checked for the type of exception in test_add_raises(). You can also check the parameters to the exception. For start_tasks_db(db_path, db_type), not only does db_type need to be a string, it really has to be either ’tiny’ or ’mongo’. You can check to make sure the exception message is correct by adding as excinfo:

ch2/tasks_proj/tests/func/test_api_exceptions.py
 def​ test_start_tasks_db_raises():
 """Make sure unsupported db raises an exception."""
 with​ pytest.raises(ValueError) ​as​ excinfo:
  tasks.start_tasks_db(​'some/great/path'​, ​'mysql'​)
  exception_msg = excinfo.value.args[0]
 assert​ exception_msg == ​"db_type must be a 'tiny' or 'mongo'"

This allows us to look at the exception more closely. The variable name you put after as (excinfo in this case) is filled with information about the exception, and is of type ExceptionInfo.

In our case, we want to make sure the first (and only) parameter to the exception matches a string.