Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python mock os.environ used inside a class

I'm trying to mock os.environ inside a class but I just can't get it right. Here's my structure:

#file.py
import os

class MyClass():
    connection_url = os.environ['DB']

#some code

And here's my test (latest try, anyways):

#test.py
from unittest import TestCase
from unittest.mock import patch
from file import MyClass

class TestMyClass(TestCase):
    @patch.dict('file.os.environ', {'DB' : 'Dummy' })
    def setUp(self):
         self.class = MyClass()

#some testing

This is failing miserably, raising KeyError 'DB'... Can someone help me? I'm new to python unittesting. I researched some blogs and stackoverflow, tried some solutions but couldn't get it right.

Thanks in advance!

like image 685
Lucas Garcia Avatar asked Feb 22 '17 04:02

Lucas Garcia


People also ask

Can we mock a class in Python?

unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. unittest.mock provides a core Mock class removing the need to create a host of stubs throughout your test suite.

How does environ work Python?

environ in Python is a mapping object that represents the user's environmental variables. It returns a dictionary having user's environmental variable as key and their values as value. os. environ behaves like a python dictionary, so all the common dictionary operations like get and set can be performed.


1 Answers

The problem here is that connection_url is set when the class is created (at import time). If you want to mock it, you can patch the attribute on the class itself before you use that attribute:

class TestMyClass(TestCase):
    @patch.object(file.MyClass, 'connection_url', 'Dummy')
    def setUp(self):
        self.instance = MyClass()

You'll still have to deal with the potential KeyError at import time -- i.e. you'll need to make sure that your test runner has a dummy value in the environment before you import file or else you'll have to modify the code in file.py to make sure that it doesn't raise a KeyError. There are a few strategies here. You can just suppress the KeyError letting connection_url = default_value if it wasn't set in the environment:

class MyClass():
    connection_url = os.environ.get('DB', default_value)

Or you can move the connection_url fetching into __init__:

class MyClass():
    def __init__(self):
        connection_url = os.environ['DB']

This latter code has the additional benefit that your patch.dict should start to work.

Note that if you choose the either of the first two approaches, the instance will only have the patched connection_url during the setUp method -- The subsequent tests won't have that patched. It's quite likely that this will be a show-stopper and you'll need to create a new instance in each test that you have:

class TestMyClass(TestCase):
    @patch.object(file.MyClass, 'connection_url', 'Dummy')
    def test_first_thing(self):
        self.instance = MyClass()
        ...

    @patch.object(file.MyClass, 'connection_url', 'Dummy')
    def test_second_thing(self):
        self.instance = MyClass()
        ...
like image 181
mgilson Avatar answered Sep 28 '22 07:09

mgilson