Assertion Error: Custom Exception not raised

Question:

I am trying to write unittests for a function I wrote that includes a try/except block.

I have raised a custom exception if my string contains less than 3 characters. I wanted to test that the error gets raised when inputting a string less than 3 characters long.

When running the function, if I input a string less than 3 characters, e.g. "ha" – I get the correct error message: "There are not enough letters in your sentence" which leads me to believe that I have raised the custom exception correctly, however, googling has told me that this means I have not raised my custom exception in my function. I just cannot see or understand where I have gone wrong.

Function file:

from collections import Counter


# set custom exception to raise when a word with less than 3 letters is given
class NotEnoughLetters(Exception):
    pass


# create a function that will return the 3 most common letters
def three_most_common(string: str):
    string = string.replace(" ", "")
    try:
        if not all(char.isalpha() for char in string):
            raise ValueError("Your input must contain a string")  # using all because in this instance I haven't accounted for strings and ints mixed

        if len(string) < 3:
            raise NotEnoughLetters("There are not enough letters in your sentence")

        most_common = Counter(string).most_common(3)
        letters = [key for key, value in most_common]

    except ValueError as err:
        return err
    except NotEnoughLetters as e:
        return e
    else:
        return f"Here are your most common letters: 1) {letters[0]}  2) {letters[1]}  3) {letters[2]}"

    finally:
        print("The program is running, please wait for your output")

Test file:

import unittest
from unittest import TestCase
from common_letters import three_most_common, NotEnoughLetters


class TestCommonLetters(TestCase):

    # valid input
    def test_good_string(self):
        expected_input = "cheesy puff"
        expected_output = "Here are your most common letters: 1) e  2) f  3) c"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)  # add assertion here

    # invalid input
    def test_bad_string(self):
        expected_input = "cheesy puff"
        false_output = "Here are your most common letters: 1) f  2) f  3) e"
        result = three_most_common(expected_input)
        self.assertNotEqual(false_output, result)

    # edge case 1, having 3 letters
    def test_having_three_letters(self):
        expected_input = "hay"
        expected_output = "Here are your most common letters: 1) h  2) a  3) y"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)

    # edge case 2, having 2 letters TODO this didn't work so get clarification tomorrow as to why not
    def test_having_two_letters(self):
        with self.assertRaises(NotEnoughLetters):
            three_most_common(string="ha")


if __name__ == '__main__':
    unittest.main()

This is giving me the following output:

Traceback (most recent call last):
  File "C:Homework-LivvyWHomework-LivvyWHomework_week7-LivvyWtest_common_letters.py", line 31, in test_having_two_letters
    with self.assertRaises(NotEnoughLetters):
AssertionError: NotEnoughLetters not raised

I have tried to look at similar stackoverflow question/answers but sadly still not comprehending why/where I have gone wrong. Thank you!

Asked By: Livvy Wilson

||

Answers:

You are not raising exception because of this block:

except NotEnoughLetters as e:
    return e

Instead you are catching it and than just returning it. Remove this block and it will be raised. The same is correct for ValueError.
With these changes your code becomes:

from collections import Counter

# set custom exception to raise when a word with less than 3 letters is given
class NotEnoughLetters(Exception):
    pass


# create a function that will return the 3 most common letters
def three_most_common(string: str):
    string = string.replace(" ", "")
    try:
        if not all(char.isalpha() for char in string):
            raise ValueError("Your input must contain a string")  # using all because in this instance I haven't accounted for strings and ints mixed

        if len(string) < 3:
            raise NotEnoughLetters("There are not enough letters in your sentence")
            
        most_common = Counter(string).most_common(3)
        letters = [key for key, value in most_common]

        return f"Here are your most common letters: 1) {letters[0]}  2) {letters[1]}  3) {letters[2]}"

    finally:
        print("The program is running, please wait for your output")

With these modifications all your 4 tests pass.

Answered By: Andrei Evtikheev

Not raise the Custom Exceptions

An other possibility is to remove your custom Exceptions an return directly the error message. So your code becomes:

from collections import Counter

# create a function that will return the 3 most common letters
def three_most_common(string: str):
    string = string.replace(" ", "")
    try:
        if not all(char.isalpha() for char in string):
            #raise ValueError("Your input must contain a string")  # using all because in this instance I haven't accounted for strings and ints mixed
            return "Your input must contain a string"

        if len(string) < 3:
            #raise NotEnoughLetters("There are not enough letters in your sentence")
            return "There are not enough letters in your sentence"

        most_common = Counter(string).most_common(3)
        letters = [key for key, value in most_common]

    #except ValueError as err:
    #    return err
    #except NotEnoughLetters as e:
    #    return e
    #else:
        return f"Here are your most common letters: 1) {letters[0]}  2) {letters[1]}  3) {letters[2]}"

    finally:
        print("The program is running, please wait for your output")

How to change the test code

Accordingly the test code becomes the following:

import unittest
from unittest import TestCase
#from common_letters import three_most_common, NotEnoughLetters
from common_letters import three_most_common

class TestCommonLetters(TestCase):

    # valid input
    def test_good_string(self):
        expected_input = "cheesy puff"
        expected_output = "Here are your most common letters: 1) e  2) f  3) c"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)  # add assertion here

    # invalid input
    def test_bad_string(self):
        expected_input = "cheesy puff"
        false_output = "Here are your most common letters: 1) f  2) f  3) e"
        result = three_most_common(expected_input)
        self.assertNotEqual(false_output, result)

    # edge case 1, having 3 letters
    def test_having_three_letters(self):
        expected_input = "hay"
        expected_output = "Here are your most common letters: 1) h  2) a  3) y"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)

    # edge case 2, having 2 letters TODO this didn't work so get clarification tomorrow as to why not
    """def test_having_two_letters(self):
        with self.assertRaises(NotEnoughLetters):
            three_most_common(string="ha")"""

    # edge case 2, having 2 letters TODO this didn't work so get clarification tomorrow as to why not
    def test_having_two_letters(self):
        result = three_most_common(string="ha")
        self.assertEqual("There are not enough letters in your sentence", result)

if __name__ == '__main__':
    unittest.main()

In the test code, the test case test_having_two_letters() verifies the error message returned by the function three_most_common() (as in the other test cases) and not if it is raised an Exception.

Answered By: frankfalse