Using mock to test if directory exists or not - unit-testing

I have been exploring mock and pytest for a few days now.
I have the following method:
def func():
if not os.path.isdir('/tmp/folder'):
os.makedirs('/tmp/folder')
In order to unit test it, I have decided to patch os.path.isdir and os.makedirs, as shown:
#patch('os.path.isdir')
#patch('os.makedirs')
def test_func(patch_makedirs, patch_isdir):
patch_isdir.return_value = False
assert patch_makedirs.called == True
The assertion fails, irrespective of the return value from patch_isdir. Can someone please help me figure out where I am going wrong?

Can't say for sure having the complete code, but I have the feeling it's related to where you're patching.
You should patch the os module that was imported by the module under test.
So, if you have it like this:
mymodule.py:
def func():
if not os.path.isdir('/tmp/folder'):
os.makedirs('/tmp/folder')
you should make your _test_mymodule.py_ like this:
#patch('mymodule.os')
def test_func(self, os_mock):
os_mock.path.isdir.return_value = False
assert os_mock.makedirs.called
Note that this specific test is not that useful, since it's essentially testing if the module os works -- and you can probably assume that is well tested. ;)
Your tests would probably be better if focused on your application logic (maybe, the code that calls func?).

You are missing the call to func().
#patch('os.path.isdir')
#patch('os.makedirs')
def test_func(patch_makedirs, patch_isdir):
patch_isdir.return_value = False
yourmodule.func()
assert patch_makedirs.called == True

Related

Pytest keep missing return line in the same function that has been ran multiple time

I'm doing a project in django and I have 2 serializer like this:
parent_serializer.py
class ParentSerializer(serializer.Serializers):
action = ChildSerializer()
child_serializer.py
class ChildSerializer(serializer.Serializers):
...
def validate(self, attrs):
...
**return attrs**
There is an if statement in the validate function and i wrote all the tests needed for the if statement, but pytest coverage keep saying that it missed the return statement line (return attrs), which imo, supposed to run in every test case.
I did try everything possible but nothing works. Please help me on that one
I found the solution.
So basically I have to cover else case in the validate function. I have a statement look like if action in: ... which actually never happened in real case, but coverage still call missing for it.

Django Model function behavior changes based on how many tests being run

I have a need for a uniqueID within my Django code. I wrote a simple model like this
class UniqueIDGenerator(models.Model):
nextID = models.PositiveIntegerField(blank=False)
#classmethod
def getNextID(self):
if(self.objects.filter(id=1).exists()):
idValue = self.objects.get(id=1).nextID
idValue += 1
self.objects.filter(id=1).update(nextID=idValue)
return idValue
tempObj = self(nextID=1)
tempObj.save()
return tempObj.nextID
Then I wrote a unit test like this:
class ModelWorking(TestCase):
def setUp(self):
return None
def test_IDGenerator(self):
returnValue = UniqueIDGenerator.getNextID()
self.assertEqual(returnValue, 1)
returnValue = UniqueIDGenerator.getNextID()
self.assertEqual(returnValue, 2)
return None
When I run this test by itself, it runs fine. No issues.
When I run this test as a suite, which includes a bunch of other unit tests as well (which include calls to getNextID() as well), this test fails. The getNextID() always returns 1. Why would that be happening?
I figured it out.
Django runs each test in a transaction to provide isolation. Doc link.
Since my other tests make a call to getNextID(), the first row gets deleted after the first test that makes such a call is complete. Subsequent tests never find (id=1), due to which all subsequent calls return the value 1.
Even though I don't think I would face that situation in production, I went I ahead and changed my code to use .first() instead of (id=1). Like this
def getNextID(self):
firstRow = self.objects.first()
if(firstRow):
That way I believe it would better handle any future scenario when the database table might be emptied.

Can global variable be used to call function?

SEFC.py:
import time
import traceback
import platform
sefc_verbose = False
obj_sefc = None
class CSEFC():
def __init__(self):
self.fp_platform = False
self.bbu_platform = False
return
def __del__(self):
return
def ipmi_cmd_trace(self):
return False
KCS.py:
import SEFC as sefc
class CKCS(CLogger):
def __init__(self, str_ip = None, int_port = _DEFAULT_ATRAGON_PORT):
CLogger.__init__(self)
self.obj_json_client = None
def send_ipmi_target(self, targetstr, raw_request, int_retry = 3):
if sefc.obj_sefc.ipmi_cmd_trace():
##do stuff
I am reading code written by someone else.I can't seem to understand in if sefc.obj_sefc.ipmi_cmd_trace(): obj_sefc is used to call ipmi_cmd_trace() function. sefc_obj is a global variable I belive. But this code should not work. Also, I doubt my programming ability. This code seems to compile and work for others. Is this correct? Am I missing something here?
With just the code you've shown, you're right, it won't work, since obj_sefc is None in the SEFC module. However, I suspect that some other code that you haven't shown that creates an instance of the CSEFC class and assigns it to that global variable. Then the code you've shown will work.
Its probably not a good design for the code you've shown to be reliant on some other code to be run first, since it will fail if it gets run in the wrong order. However, using a global variable to contain a single instance of a class is not problematic in general. You just want to make sure the code that creates the instance is put somewhere that ensures it will be run before the instance is needed. For instance, the code could be put at the bottom of the SEFC module, or at the top of the KCS module.

Skipping a test based on the pass of a previous test

I'm using the django test suite with nose and trying to figure out how I can run a followup test if a previous test fails. Is that possible?
Here is how I'd do it:
Have a global variable called test_condition_a = False shared among test cases.
Try/catch assert in test_a and catch the exception when it fails so that you can set test_condition_a = True before the test exits.
Use #unittest.skipIf(test_condition_a) on all other test cases that you want run only if test_a fails.
EDIT On second thought, the above is not going to work as the test order is random. Your best bet would be to do something like this
class Test(TestCase):
def setUp(self):
...
#unittest.skip("Skip by default")
def testB(self):
#test code
def testA(self):
try:
#test code
return True
except Error:
return False
def testA_then_B(self):
if (self.testA()):
self.testB()

Unmocking a mocked object in Django unit tests

I have several TestCase classes in my django application. On some of them, I mock out a function which calls external resources by decorating the class with #mock.patch, which works great. One TestCase in my test suite, let's call it B(), depends on that external resource so I don't want it mocked out and I don't add the decorator. It looks something like this:
#mock.patch("myapp.external_resource_function", new=mock.MagicMock)
class A(TestCase):
# tests here
class B(TestBase):
# tests here which depend on external_resource_function
When I test B independently, things work as expected. However, when I run both tests together, A runs first but the function is still mocked out in B. How can I unmock that call? I've tried reloading the module, but it didn't help.
Patch has start and stop methods. Based on what I can see from the code you have provided, I would remove the decorator and use the setUp and tearDown methods found in the link in your classes.
class A(TestCase):
def setUp(self):
self.patcher1 = patch('myapp.external_resource_function', new=mock.MagicMock)
self.MockClass1 = self.patcher1.start()
def tearDown(self):
self.patcher1.stop()
def test_something(self):
...
>>> A('test_something').run()
Great answer. With regard to Ethereal's question, patch objects are pretty flexible in their use.
Here's one way to approach tests that require different patches. You could still use setUp and tearDown, but not to do the patch.start/stop bit.
You start() the patches in each test and you use a finally clause to make sure they get stopped().
Patches also support Context Manager stuff so that's another option, not shown here.
class A(TestCase):
patcher1 = patch('myapp.external_resource_function', new=mock.MagicMock)
patcher2 = patch('myapp.something_else', new=mock.MagicMock)
def test_something(self):
li_patcher = [self.patcher1]
for patcher in li_patcher:
patcher.start()
try:
pass
finally:
for patcher in li_patcher:
patcher.stop()
def test_something_else(self):
li_patcher = [self.patcher1, self.patcher2]
for patcher in li_patcher:
patcher.start()
try:
pass
finally:
for patcher in li_patcher:
patcher.stop()