I have a function
def execute(process: subprocess.Popen):
if not isinstance(process, subprocess.Popen):
raise ValueError('expected: subprocessPopen, found: %s' % type(process))
data = io.TextIOWrapper(process.stdout, encoding='utf-8')
func1(data_input)
I want to unit test it, where I want data as a dictionary which is to be passed to func1 as an argument but not able to find a way out.
Have a look at the spec argument to the Mock object, in order to make it pass the isinstance() test:
https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock
Have a look at the patch decorator for how to replace the process.stdout member with, e.g., a io.BytesIO object:
https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch
You also might want to consider the examples given here:
https://docs.python.org/3/library/unittest.mock.html#quick-guide
import mock
def test_run_func():
subprocess.Popen = mock.MagicMock(return_value=expected_return_value)
# The rest of your test...
Something like should work.
Related
I am trying to figure out how to know if a method of class is being called inside a method.
following is the code for the unit test:
# test_unittes.py file
def test_purge_s3_files(mocker):
args = Args()
mock_s3fs = mocker.patch('s3fs.S3FileSystem')
segment_obj = segments.Segmentation()
segment_obj.purge_s3_files('sample')
mock_s3fs.bulk_delete.assert_called()
inside the purge_s3_file method bulk_delete is called but when asserting it says that the method was expected to be called and it is not called!
mocker = <pytest_mock.plugin.MockerFixture object at 0x7fac28d57208>
def test_purge_s3_files(mocker):
args = Args()
mock_s3fs = mocker.patch('s3fs.S3FileSystem')
segment_obj = segments.Segmentation(environment='qa',
verbose=True,
args=args)
segment_obj.purge_s3_files('sample')
> mock_s3fs.bulk_delete.assert_called()
E AssertionError: Expected 'bulk_delete' to have been called.
I don't know how to test this and how to assert if the method is called!
Below you can find the method being testing:
# segments.py file
import s3fs
def purge_s3_files(self, prefix=None):
bucket = 'sample_bucket'
files = []
fs = s3fs.S3FileSystem()
if fs.exists(f'{bucket}/{prefix}'):
files.extend(fs.ls(f'{bucket}/{prefix}'))
else:
print(f'Directory {bucket}/{prefix} does not exist in s3.')
print(f'Purging S3 files from {bucket}/{prefix}.')
print(*files, sep='\n')
fs.bulk_delete(files)
The problem you are facing is that the mock you are setting up is mocking out the class, and you are not using the instance to use and check your mocks. In short, this should fix your problem (there might be another issue explained further below):
m = mocker.patch('s3fs.S3FileSystem')
mock_s3fs = m.return_value # (or mock_s3())
There might be a second problem in how you are not referencing the right path to what you want to mock.
Depending on what your project root is considered (considering your comment here) your mock would need to be referenced accordingly:
mock('app.segments.s3fs.S3FileSystem')
The rule of thumb is that you always want to mock where you are testing.
If you are able to use your debugger (or output to your console) you will (hopefully :)) see that your expected call count will be inside the return_value of your mock object. Here is a snippet from my debugger using your code:
You will see the call_count attribute set to 1. Pointing back to what I mentioned at the beginning of the answer, by making that change, you will now be able to use the intended mock_s3fs.bulk_delete_assert_called().
Putting it together, your working test with modification runs as expected (note, you should also set up the expected behaviour and assert the other fs methods you are calling in there):
def test_purge_s3_files(mocker):
m = mocker.patch("app.segments.s3fs.S3FileSystem")
mock_s3fs = m.return_value # (or m())
segment_obj = segments.Segmentation(environment='qa',
verbose=True,
args=args)
segment_obj.purge_s3_files('sample')
mock_s3fs.bulk_delete.assert_called()
Python mock testing depends on where the mock is being used. So you have the mock the function calls where it is imported.
Eg.
app/r_executor.py
def r_execute(file):
# do something
But the actual function call happens in another namespace ->
analyse/news.py
from app.r_executor import r_execute
def analyse():
r_execute(file)
To mock this I should use
mocker.patch('analyse.news.r_execute')
# not mocker.patch('app.r_executor.r_execute')
main.py
def sendContent(path = config.path):
contentData = content.extractData(path)
jsonContent = json.dumps(contentData, ensure_ascii=False, indent=4, sort_keys=True)
logging.info("Creating json...")
return config.createContent(jsonContent)
I want to test attributes called by this method. For this, I have to mock method. However, I probably didn't understand how to do because, if I don't specify the attribute in my mock, it doesn't use default parameter (config.path).
Here (one of) my (many) test :
tests/testMain.py
def testSendContent():
main.sendContent = Mock()
main.sendContent()
main.sendContent.assert_called_with(config.path)
AssertionError: expected call not found
If, line 3, I put main.sendContent(config.path), the test is OK of course... but if I don't put anything, mock doesn't use default parameter.
Thank you for your help
This mock method call main.sendContent() on the second line of 'testSendContent' is not the real call of your sendContent function, it is just a replacement mock function call. Since it is called with no arguments, not with the real function's default ones as you expected.
So instead of checking it with assert_called_with, you can just use your mock call fit for purpose:
If you want to bypass the function call of sendContent within the test process
you can continue with mocking it with specifying the expected return value like
this:
main.sendContent = Mock(return_value = "SOME_PATH_HERE")
Alternatively, if you want to test sendContent only, don't use mock. Just call it and use :
self.assertEqual(sendContent(), "SOME_PATH")
I'm trying to test a method that requires the use of json.load in Python 3.6.
And after several attempts, I tried running the test "normally" (with the usual unittest.main() from the CLI), and in the iPython REPL.
Having the following function (simplified for purpose of the example)
def load_metadata(name):
with open("{}.json".format(name)) as fh:
return json.load(fh)
with the following test:
class test_loading_metadata(unittest2.TestCase):
#patch('builtins.open', new_callable=mock_open(read_data='{"disabled":True}'))
def test_load_metadata_with_disabled(self, filemock):
result = load_metadata("john")
self.assertEqual(result,{"disabled":True})
filemock.assert_called_with("john.json")
The result of the execution of the test file, yields a heart breaking:
TypeError: the JSON object must be str, bytes or bytearray, not 'MagicMock'
While executing the same thing in the command line, gives a successful result.
I tried in several ways (patching with with, as decorator), but the only thing that I can think of, is the unittest library itself, and whatever it might be doing to interfere with mock and patch.
Also checked versions of python in the virtualenv and ipython, the versions of json library.
I would like to know why what looks like the same code, works in one place
and doesn't work in the other.
Or at least a pointer in the right direction to understand why this could be happening.
json.load() simply calls fh.read(), but fh is not a mock_open() object. It's a mock_open()() object, because new_callable is called before patching to create the replacement object:
>>> from unittest.mock import patch, mock_open
>>> with patch('builtins.open', new_callable=mock_open(read_data='{"disabled":True}')) as filemock:
... with open("john.json") as fh:
... print(fh.read())
...
<MagicMock name='open()().__enter__().read()' id='4420799600'>
Don't use new_callable, you don't want your mock_open() object to be called! Just pass it in as the new argument to #patch() (this is also the second positional argument, so you can leave off the new= here):
#patch('builtins.open', mock_open(read_data='{"disabled":True}'))
def test_load_metadata_with_disabled(self, filemock):
at which point you can call .read() on it when used as an open() function:
>>> with patch('builtins.open', mock_open(read_data='{"disabled":True}')) as filemock:
... with open("john.json") as fh:
... print(fh.read())
...
{"disabled":True}
The new argument is the object that'll replace the original when patching. If left to the default, new_callable() is used instead. You don't want new_callable() here.
I'm using python 2.7 and delving into TDD. I'm trying to test a simple function that uses the csv module and returns a csv.reader object. I want to test that the correct type of object is being returned with the assertIsInstance test however I'm having trouble figuring out how to make this work.
#!/usr/bin/python
import os, csv
def importCSV(fileName):
'''importCSV brings in the CSV transaction file to be analyzed'''
try:
if not(os.path.exists("data")):
os.makedirs("data")
except(IOError):
return "Couldn't create data directory!"
try:
fileFullName = os.path.join("data", fileName)
return csv.reader(file(fileFullName))
except(IOError):
return "File not found!"
The test currently looks like this....
#!/usr/bin/python
from finaImport import finaImport
import unittest, os, csv
class testImport(unittest.TestCase):
'''Tests for importing a CSV file'''
def testImportCSV(self):
''' Test a good file and make sure importCSV returns a csv reader object '''
readerObject = finaImport.importCSV("toe")
self.assertTrue(str(type(readerObject))), "_csv.reader")
I really don't think wrapping "toe" in a str and type function is correct. When I try something like...
self.assertIsInstance(finaImport.importCSV("toe"), csv.reader)
It returns an error like...
TypeError: isinstance() arg2 must be a class, type, or tuple of classes and types
Help???
self.assertTrue(str(type(readerObject)), "_csv.reader")
I don't think that your first test (above) is so bad (I fixed a small typo there; you had an extra closing parenthesis). It checks that the type name is exactly "_csv.reader". On the other hand, the underscore in "_csv" tells you that this object is internal to the csv module. In general, you shouldn't be concerned about that.
Your attempt at the assertIsInstance test is flawed in that csv.reader is a function object. If you try it in the REPL, you see:
>>> import csv
>>> csv.reader
<built-in function reader>
Often, we care less about the type of an object and more about whether it implements a certain interface. In this case, the help for csv.reader says:
>>> help(csv.reader)
... The returned object is an iterator. ...
So, you could do the following test (instead or in addition to your other one):
self.assertIsInstance(readerObject, collections.Iterator)
You'll need a import collections for that, of course. And, you might want to test that the iterator returns lists of strings, or something like this. That would allow you to use something else under the hood later and the test would still pass.
I have a module, ui. I'd like to get a list of all the classes that ui defines which are a subclass of ui.View, and that can be called without arguments.
I've tried the following code:
ui_elements=[d for d in ui.__dict__.values() if type(d)==type and inspect.getargspec(d.__init__)[0]==1 and isinstance(d(),ui.View)]
But I get the following error from inside the inspect module:
TypeError: <slot wrapper '__init__' of 'object' objects> is not a Python function
I'm using inspect instead of a try: except: just because checking beforehand seems like a better solution than trying it to see if it works.
So, how can I get a list of all the classes that subclass ui.View and do not require arguments to create? I'd like to do this inline if possible, and I'd also like to avoid using try:/except: statements.
To get a list comprehension with try/except you can use a local function:
def tryCreate(class_):
try:
return class_()
except:
return None
ui_elements = set(tryCreate(d) for d in ui.__dict__.values()
if issubclass(d, ui.View)) - set(None)
Okay, it's a set comprehension in the end ;-)
As said in comments, you should use try/catch, but if you really want to do it that way...
First, use inspect to check if your symbol is a class, use issubclass as suggested in comments and you want the number of arguments to be 1, not the first argument to be 1. So, something like this:
ui_elements = [ d for d in ui.__dict__.values()
if (inspect.isclass(d) and
issubclass(d, ui.View) and
inspect.ismethod(d.__init__) and
len(inspect.getargspec(d.__init__)[0]) == 1) ]