mock `readlines()` in python unit tests - python-3.x

I am trying to mock a readlines() object in python unit tests. What I have so far is
class Sample:
def read_file(filename):
with open(filename, "r") as f:
lines = f.readlines()
I want to write a unit test for readlines() object.
So far, I have come up with the following.
TEST_DATA = "test\ntest2\n"
#mock.patch("builtins.open")
def test_open(mock_open):
mock_open.side_effect = [mock_open(read_data=TEST_DATA).return_value]
assert mock_open.side_effect == Sample.read_file()
My question here is, how do I assert the returned value of mock_open is the same as the returned value of the actual read_file function in the Sample class? This is where I am failing and not able to go any further. Any help on this is much appreciated! Thank you in advance!

In unittest.mock docs there is an example that may help you
Here is the docs example adapted to your code.
from unittest.mock import patch
class Sample:
def read_file(filename):
with open(filename, "r") as f:
lines = f.readlines()
return lines
TEST_DATA = "test\ntest2\n"
def test_open(mock_open):
with patch('__main__.open', mock_open(read_data=TEST_DATA)) as m:
s = Sample()
res = s.read_file('foo')
assert res == TEST_DATA

Related

How to assert contents written to io.BytesIO in unit tests

Assume the following minimal code:
def upload(fileobj, key):
# e.g. S3.Bucket.upload_fileobj()
pass
def work():
with io.BytesIO() as tmp:
tmp.write(b'abc') # here can be a tarfile created
upload(tmp, "file")
I would like to assert the contents of tmp in unit tests. How to do it correctly?
This way does not work:
from unittest.mock import MagicMock
upload = MagicMock()
work()
with upload.mock_calls[0][1][0] as f:
f.readlines() # ValueError: I/O operation on closed file.
I've figured out one solution by myself (thanks to Tim Roberts inspirations in comments), which is using side_effect option (of unittest.mock.MagicMock constructor).
import unittest.mock
eavesdropped = []
def upload_mock(fileobj, key):
fileobj.seek(0)
eavesdropped.append(fileobj.read())
with unittest.mock.patch('__main__.upload', side_effect=upload_mock) as upl:
work()
assert eavesdropped[0] == b'abc'
work()
assert eavesdropped[1] == b'abc'

How can I redirect hardcoded calls to open to custom files?

I've written some python code that needs to read a config file at /etc/myapp/config.conf . I want to write a unit test for what happens if that file isn't there, or contains bad values, the usual stuff. Lets say it looks like this...
""" myapp.py
"""
def readconf()
""" Returns string of values read from file
"""
s = ''
with open('/etc/myapp/config.conf', 'r') as f:
s = f.read()
return s
And then I have other code that parses s for its values.
Can I, through some magic Python functionality, make any calls that readconf makes to open redirect to custom locations that I set as part of my test environment?
Example would be:
main.py
def _open_file(path):
with open(path, 'r') as f:
return f.read()
def foo():
return _open_file("/sys/conf")
test.py
from unittest.mock import patch
from main import foo
def test_when_file_not_found():
with patch('main._open_file') as mopen_file:
# Setup mock to raise the error u want
mopen_file.side_effect = FileNotFoundError()
# Run actual function
result = foo()
# Assert if result is expected
assert result == "Sorry, missing file"
Instead of hard-coding the config file, you can externalize it or parameterize it. There are 2 ways to do it:
Environment variables: Use a $CONFIG environment variable that contains the location of the config file. You can run the test with an environment variable that can be set using os.environ['CONFIG'].
CLI params: Initialize the module with commandline params. For tests, you can set sys.argv and let the config property be set by that.
In order to mock just calls to open in your function, while not replacing the call with a helper function, as in Nf4r's answer, you can use a custom patch context manager:
from contextlib import contextmanager
from types import CodeType
#contextmanager
def patch_call(func, call, replacement):
fn_code = func.__code__
try:
func.__code__ = CodeType(
fn_code.co_argcount,
fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
fn_code.co_code,
fn_code.co_consts,
tuple(
replacement if call == name else name
for name in fn_code.co_names
),
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars,
)
yield
finally:
func.__code__ = fn_code
Now you can patch your function:
def patched_open(*args):
raise FileNotFoundError
with patch_call(readconf, "open", "patched_open"):
...
You can use mock to patch a module's instance of the 'open' built-in to redirect to a custom function.
""" myapp.py
"""
def readconf():
s = ''
with open('./config.conf', 'r') as f:
s = f.read()
return s
""" test_myapp.py
"""
import unittest
from unittest import mock
import myapp
def my_open(path, mode):
return open('asdf', mode)
class TestSystem(unittest.TestCase):
#mock.patch('myapp.open', my_open)
def test_config_not_found(self):
try:
result = myapp.readconf()
assert(False)
except FileNotFoundError as e:
assert(True)
if __name__ == '__main__':
unittest.main()
You could also do it with a lambda like this, if you wanted to avoid declaring another function.
#mock.patch('myapp.open', lambda path, mode: open('asdf', mode))
def test_config_not_found(self):
...

How can I write a for loop that writes a code template to file in python 3.7

I was wondering if there is a way to write a template to file inside of a for loop. The code below is an example of what I have written so far. The variable inside of the open function equals 'python.py'.
with open(filename, 'w') as fout:
x = 0
for x in range(5):
fout.write(""" def function_number{}(): \n
""".format(x))
Here is what I am expecting to happen
def function_number0():
def function_number1():
def function_number2():
def function_number3():
def function_number4():
Here is the output I receive
def function_number0():
def function_number1():
def function_number2():
def function_number3():
def function_number4():
How can I restructure my code to get the desired output?

How to mock readlines() in Python unit tests

I am trying to write a unit test to a class init that reads from a file using readlines:
class Foo:
def __init__(self, filename):
with open(filename, "r") as fp:
self.data = fp.readlines()
with sanity checks etc. included.
Now I am trying to create a mock object that would allow me to test what happens here.
I try something like this:
TEST_DATA = "foo\nbar\nxyzzy\n"
with patch("my.data.class.open", mock_open(read_data=TEST_DATA), create=True)
f = Foo("somefilename")
self.assertEqual(.....)
The problem is, when I peek into f.data, there is only one element:
["foo\nbar\nxyzzy\n"]
Which means whatever happened, did not get split into lines but was treated as one line. How do I force linefeeds to happen in the mock data?
This will not work with a class name
with patch("mymodule.class_name.open",
But this will work by mocking the builtin directly, builtins.open for python3
#mock.patch("__builtin__.open", new_callable=mock.mock_open, read_data=TEST_DATA)
def test_open3(self, mock_open):
...
or this without class by mocking the module method
def test_open(self):
with patch("mymodule.open", mock.mock_open(read_data=TEST_DATA), create=True):
...
#Gang's answer pointed me to the right direction but it's not a complete working solution. I have added few details here which makes it a working code without any tinkering.
# file_read.py
def read_from_file():
# Do other things here
filename = "file_with_data"
with open(filename, "r") as f:
l = f.readline()
return l
# test_file_read.py
from file_read import read_from_file
from unittest import mock
import builtins
##mock.patch.object(builtins, "open", new_callable=mock.mock_open, read_data="blah")
def test_file_read(mock_file_open):
output = read_from_file()
expected_output = "blah"
assert output == expected_output

Python: mock file input for testing function

I know there are similar posts, but I did not find anything like this one.
I have a function in python that receives as input the filename to be read and process and return something, and I want to test if the output for my function. Example:
#main function
def myfunction(filename):
f=open(filename)
for line in f:
# process data
pass
f.close()
return # something
#test function for the main function
def test_myfunction():
mockfile = #mymockfile
assert myfunction(mockfile) == #something
How could I create a mock file to test this function without having to write a read file?
This was the closest I found to emulate what i need (http://www.voidspace.org.uk/python/mock/helpers.html#mock-open)
Having struggled with the same question, please find my answers below.
update December 2022
I found a simpler solution than my original solution from 2018. Using Python 3.9.2 and running the script from the command line with pytest:
import unittest.mock
#main function
def myfunction(filename):
f=open(filename)
maximum = 0
for line in f:
if maximum < len(line):
maximum = len(line)
pass
f.close()
return maximum
def test_myfunction():
mock_file = unittest.mock.mock_open(read_data=('12characters\n13_characters'))
with unittest.mock.patch('builtins.open', mock_file):
actual_result = myfunction('foo')
assert not actual_result == 12
assert actual_result == 13
original answer 2018
I used Python 3.6 and Py.test through the pydev plugin in Eclipse.
import unittest.mock as mock
from unittest.mock import mock_open
#main function
def myfunction(filename):
f=open(filename)
maximum = 0
for line in f:
if maximum < len(line):
maximum = len(line)
pass
f.close()
return maximum
#test function for the main function
#mock.patch('builtins.open', new_callable=mock_open, create=True)
def test_myfunction(mock_open):
mock_open.return_value.__enter__ = mock_open
mock_open.return_value.__iter__ = mock.Mock(
return_value = iter(['12characters', '13_characters']))
answer = myfunction('foo')
assert not answer == 12
assert answer == 13

Resources