2

I would like to know how to write Python 3 unittest for try exceptblocks that are defined outside of function definitions in Python's module.

Imagine that in package/module.py I have a block of code like:

import os

try:
  CONSTANT = os.environ['NOT_EXISTING_KEY']
except KeyError:
  CONSTANT = False
finally:
  del os

(please don't mind the actual code, I know I could have used os.getenv('NOT_EXISTING_KEY', False)in this specific case, what I am interested in is really testing that the try-except block in a module (outside of a function) behaves as expected.

How can I write a unit test that checks that package.module.CONSTANT is set to the expected value?

In the unittest file (I use pytest) I have something like:

from package.module import CONSTANT

def test_constant_true():
  assert CONSTANT == 'expected_value'

to test that if the try block executed correctly then CONSTANT is as expected.

I don't know, however, how to mock the import machinery so that the os.environ in the try block raises an exception and I can test that CONSTANT is set to False.

How can I do that?

lucacerone
  • 9,859
  • 13
  • 52
  • 80

2 Answers2

2

You can use monkeypatch to set the environment variable, but you have to reload the module for the change to take effect:

from importlib import reload

from package import module


def test_constant_true(monkeypatch):
    monkeypatch.setenv('MY_KEY', '42')
    reload(module)
    assert module.CONSTANT == '42'


def test_constant_false():
    reload(module)
    assert not module.CONSTANT

Given this content of package/module.py:

import os

try:
    CONSTANT = os.environ['MY_KEY']
except KeyError:
    CONSTANT = False
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • I think the answer could be improved by using a mock that raises the desired error to test that the exception is caught correctly but the "reload" function was the key I needed to solve my issue, many thanks! – lucacerone Apr 17 '20 at 04:58
  • I would argue that the exception is an implementation detail that needs not to be tested - the relevant part is the set variable - but that is of course opinion-based. Glad I could help! – MrBean Bremen Apr 17 '20 at 06:37
0

You could mock the environment using mock.patch.dict and import the value inside your unit test method. Like so:

from unittest import TestCase, mock

class YourTest(TestCase):
    @mock.patch.dict('os.environ', {'NOT_EXISTING_KEY': 'value'})
    def test_constant_key_defined(self, mocked):
        """ Tests when the key is defined """
        from package.module import CONSTANT
        self.assertEqual(CONSTANT, 'value')

    def test_constant_key_not_defined(self):
        """ Tests when the key is not defined """
        from package.module import CONSTANT
        self.assertEqual(CONSTANT, 'value')

You may use the importlib.reload, like @mrbean-bremen's answer, which I am not familiar with.

meyer1994
  • 377
  • 2
  • 11
  • this wouldn't let me happen to test what happen when an error is raised though – lucacerone Apr 15 '20 at 15:56
  • If you want to test when the error is raised, simply remove the mock. Just create another method without the mock and do the same thing. I will update the answer. – meyer1994 Apr 15 '20 at 15:58