I have the following configuration class:
class ConfigB(object):
Id = None
fileName = None
def __init__(self, file):
self.Id = self.searchForId(file)
self.fileName = file
Which is instantiated multiple times in the following class and the properties accessed:
from config.ConfigB import ConfigB
class FileRunner(object):
def runProcess(self, cfgA)
for file in cfgA.listFiles:
cfgB = ConfigB(file)
print(cfgB.Id)
print(cfgB.fileName)
To test it I created the following test class where I mock ConfigB for the FileRunner class:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
@mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
cfgA = Mock()
cfgA.listFiles = ['File1','File2']
cfgB().Id.side_effect = [1,2]
cfgB().fileName.side_effect = ['File1','File2']
fileRunner = FileRunner()
fileRunner.runProcess(cfgA)
I am trying to to get the mock for cfgB to return multiple values for both 'Id' and 'fileName'. If I use cfgB().fileName = 'File1'
, I can get the mock for cfgB to return 'File1' twice, but I would prefer if I could iterate through multiple return values. Is something that can be done?
*Edit: I would like to make clear that the above test does not work for returning specific values and instead I get the following output:
<MagicMock name='cfgB().Id' id='160833320'>
<MagicMock name='cfgB().fileName' id='160833320'>
<MagicMock name='cfgB().Id' id='160833320'>
<MagicMock name='cfgB().fileName' id='160833320'>
The problem here is that you are not actually using side_effect
the way it is intended to be used.
Per the documentation here, the side_effect
attribute states:
A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value.
The key thing to realize here is function. The expectation here is something that is actually called. You are actually testing attributes, and attributes are not being called like a function, so you are not actually configuring your test properly with how you are using those side_effect calls.
Based on what you are looking to test, you should take a slightly different approach. Looking at your code, you are looking to create a ConfigB
object inside your loop as you iterate over cfgA.listFiles
. So, this indicates that you are actually looking to control the side_effect
of what happens when you call ConfigB(file)
which you have mock patched as cfgB
in your test.
Furthermore, you are passing what seems like the filenames from iterating over cfgA.listFiles
to configB
. Therefore, you can just set listFiles
as a list of arbitrary filenames as:
cfgA.listFiles = ['some_file_name_1', 'some_file_name_2']
Then, all you need to do, is then set your cfgB
mock's side_effect to now return a Mock
object containing the attributes of interest to properly proceed with your testing, as such:
cfgB.side_effect = [
Mock(Id="some_id_1", fileName="some_filename_1"),
Mock(Id="some_id_2", fileName="some_filename_2")
]
Running with those modifications, will then yield the following results from your print statements you have in your code:
some_id_1
some_filename_1
some_id_2
some_filename_2
So, as you can see, now we have successfully set up your iterable to hold the filenames you want to set up for your tests. Furthermore, the side_effect
is now properly used on your mock of ConfigB
, in order to now return the proper mocked config object holding the attributes that you can test with in each iteration.
Here is what the final test method looks like all put together:
class TestFileRunner(unittest.TestCase):
@mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
cfgA = Mock()
cfgA.listFiles = ['some_file_name_1', 'some_file_name_2']
cfgB.side_effect = [
Mock(Id="some_id_1", fileName="some_filename_1"),
Mock(Id="some_id_2", fileName="some_filename_2")
]
fileRunner = FileRunner()
fileRunner.runProcess(cfgA)