Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Unit Test for Try/Except Block

I have the following function with unit tests:

#!/usr/bin/env python3
# https://docs.python.org/3/library/ipaddress.html
# https://docs.python.org/3.4/library/unittest.html

import ipaddress
import unittest

from unittest.mock import patch
from unittest import TestCase

def validate_IP():
    """Prompt user for IPv4 address, then validate."""

    while True:
        try:
            return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
        except ValueError:
            print('Bad value, try again.')


class validate_IP_Test(unittest.TestCase):

    @patch('builtins.input', return_value='192.168.1.1')
    def test_validate_IP_01(self, input):
        self.assertIsInstance(validate_IP(), ipaddress.IPv4Address)

    @patch('builtins.input', return_value='10.0.0.1')
    def test_validate_IP_02(self, input):
        self.assertIsInstance(validate_IP(), ipaddress.IPv4Address)

    @patch('builtins.input', return_value='Derp!')
    def test_validate_IP_03(self, input):
        self.assertRaises(ValueError, msg=none)


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

The function uses the ipaddress module in Python3 to validate user input, i.e. checks that user input is an actual IPv4 address. My first two tests work as expected. I'm not clear, though, on how to test for invalid input using Python3's unittest module for the exception portion of the function, as in the third test.

When invalid input is entered, the test should recognize that an exception was thrown and pass the test. For reference, here's the relevant output from an interpreter when I enter an invalid address:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File 
raise AddressValueError("Expected 4 octets in %r" % ip_str)
ipaddress.AddressValueError: Expected 4 octets in 'derp'`
like image 433
marshki Avatar asked Sep 19 '25 06:09

marshki


2 Answers

You can use the assertRaises method as a context manager:

@patch('builtins.input', return_value='Derp!')
def test_validate_IP_03(self, input):
    with self.assertRaises(ValueError):
        validate_IP()

However, your validate_IP function catches the exception itself within an infinite loop so the above test won't actually pass. You can make it not retry, and re-raise the exception after outputting the error message instead, if you intend to be able to catch the exception outside the call:

def validate_IP():
    try:
        return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
    except ValueError:
        print('Bad IPv4 address.')
        raise
like image 190
blhsing Avatar answered Sep 20 '25 20:09

blhsing


The exception is not recognized because you catch it, you need to not catch it or re-raise it. To re-raise just call raise without argument, inside a catch block, in your example:

 def validate_IP():
    """Prompt user for IPv4 address, then validate."""

    while True:
        try:
            return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
        except ValueError:
            print('Bad value, try again.')
            raise

This will make you step out of that while, making it non-sense. I would move that while for another method, or function so that you can test IPv4Address raising behavior alone.

Another problem is calling input inside a function, is very annoying for testing. I would go for def validate_ip(ip):, much easier to test.

Regards,

like image 23
geckos Avatar answered Sep 20 '25 21:09

geckos