Mocking selective file writes in python unittest - python

I looked around a bit on SO but didn't find what I am looking for, I am pretty sure this has been answered elsewhere. So I have two file writes in my function like this:
def write_files():
with open("a.txt", 'w') as f_h:
f_h.write("data1")
with open("b.txt", 'w') as f_h:
f_h.write("data2")
How do I mock the f_h.write() selectively so that one returns an exception and the other does not? I tried to set side_effect but its not clear where that fits. The test code that I have experimented with has something like this:
from unittest.mock import patch, call, mock_open
import unittest
class Tester(unittest.TestCase):
def test_analyze(self):
with patch("builtins.open", mock_open(read_data="data")) as mf:
# mf.side_effect = [None, Exception()] ?
write_files()
if __name__ == '__main__':
unittest.main()

Two things: you have to mock the context manager, e.g. the result of __enter__, and you have to put the side effect on the write method of the mocked file handle (e.g. the result of the __enter__ call):
class Tester(unittest.TestCase):
def test_analyze(self):
with patch("builtins.open", mock_open(read_data="data")) as mf:
fh_mock = mf.return_value.__enter__.return_value
fh_mock.write.side_effect = [None, Exception]
with self.assertRaises(Exception):
write_files()

Related

How to check what was written to a mock_open() fake file?

I have a productive function that read a file and in some situations also write (more than once) to another file (with kind of detailed error output).
I am able to mock the file reading and give specific file content in the unittests. But I do not know how to test for what was written to the the second file which is also mocked via mock_open().
One important point is that I am not interested in writing a real file to the filesystem when unittesting.
That is the productive code:
import pathlib
def my_prod_code(fp):
with fp.open('r') as if:
result = if.read()
with fp.with_suffix('.error.out').open('w') as of:
of.write(f'Read {result}.')
of.write('FIN.')
return result
That is the unittest
import unittest
from unittest import mock
import pathlib
class MyTest(unittest.TestCase):
def test_foobar(self, mock_unlink):
opener = mock.mock_open(read_data='foobar')
with mock.patch('pathlib.Path.open', opener):
result = my_prod_code(pathlib.Path('file.in'))
self.assertEqual(result, 'foobar')
# Want to check for the written content also
There is no built-in way to do this, so you either have to add your own handling in the mock, or use some package that fakes the file system.
Adding your own handling would mean to implement your own write, e.g. something like this:
class MockWriter:
"""Collect all written data."""
def __init__(self):
self.contents = ''
def write(self, data):
self.contents += data
class MyTest(unittest.TestCase):
def test_foobar(self):
opener = mock.mock_open(read_data='foobar')
writer = MockWriter()
# replace the write method in the mock with your own
opener.return_value.write = writer.write
with mock.patch('pathlib.Path.open', opener) as f:
result = my_prod_code(pathlib.Path('file.in'))
self.assertEqual(result, 'foobar')
self.assertEqual(writer.contents, 'Read foobar.FIN.')
The other possibility is to use a fake file system like pyfakefs:
from pyfakefs.fake_filesystem_unittest import TestCase
class MyTest(TestCase):
def setUp(self):
self.setUpPyfakefs()
def test_foobar(self):
self.fs.create_file('file.in', contents='foobar')
result = my_prod_code(pathlib.Path('file.in'))
self.assertEqual(result, 'foobar')
path = pathlib.Path('file.error.out')
self.assertEqual(path.read_text(), 'Read foobar.FIN.')
This way, you don't have to do the mocking yourself and can use the standard file system functions, with the downside that you need an extra package that generates some test overhead.
Disclaimer:
I'm a contributor to pyfakefs.

How to check if mock functions has been called?

I'm writing unit tests for a simple function that writes bytes into s3:
import s3fs
def write_bytes_as_csv_to_s3(payload, bucket, key):
fs = s3fs.S3FileSystem()
fname = f"{bucket}/{key}"
print(f"writing {len(payload)} bytes to {fname}")
with fs.open(fname, "wb") as f:
f.write(payload)
return fname
def test_write_bytes_as_csv_to_s3(mocker):
s3fs_mock = mocker.patch('s3fs.S3FileSystem')
open_mock = mocker.MagicMock()
# write_mock = mocker.MagicMock()
# open_mock.write.return_value = write_mock
s3fs_mock.open.invoke.return_value = open_mock
result = write_bytes_as_csv_to_s3('awesome'.encode(), 'random', 'key')
assert result == 'random/key'
s3fs_mock.assert_called_once()
open_mock.assert_called_once()
# write_mock.assert_called_once()
How can I check if method open and write has been called once? Not sure how to set mocker to cover my case.
The unit-test you written above is perfect and mostly covered all the functionality of the methods which you want to test.
In pytest, there is a functionality to get the unittest coverage report which will show the lines covered by unittest.
Kindly install the pytest plugin html-report(if not installed) and execute the following document:-
py.test --cov=<filename to cover: unittest> --cov-report=html <testfile>
After that, you would likely found a html file in the current location o/r in the htmlconv/ directory. And from that, you could easily figure it out about the line covered and also the percentage of the unittest test coverage.
The issue is understanding how each mock is created and what exactly it mocks. For example mocker.patch('s3fs.S3FileSystem') returns a mock of s3fs.S3FileSystem, not the instance returned by calling s3fs.S3FileSystem(). Then to mock with fs.open(fname, "wb") as f you need to mock what the __enter__ dunder method returns. Hopefully the following code makes the relations clear:
def test_write_bytes_as_csv_to_s3(mocker):
# Mock of the open's context manager
open_cm_mock = mocker.MagicMock()
# Mock of the object returned by fs.open()
open_mock = mocker.MagicMock()
open_mock.__enter__.return_value = open_cm_mock
# Mock of the new instance returned by s3fs.S3FileSystem()
fs_mock = mocker.MagicMock()
fs_mock.open.return_value = open_mock
# Patching of s3fs.S3FileSystem
mocker.patch('s3fs.S3FileSystem').return_value = fs_mock
# Running the tested code and making assertions
result = write_bytes_as_csv_to_s3('awesome'.encode(), 'random', 'key')
assert result == 'random/key'
assert open_cm_mock.write.call_count == 1

HTMLTestRunner error:raise TypeError("{} is not callable".format(repr(test)))

When I use HTMLTestRunner for Python 3.5,it shows an error.
I have changed the HTMLTestRunner for support python 3.5.
The code :
import pymysql
import pymysql
import unittest
import time
import unittest.suite
import HTMLTestRunner
import sys
def hell(a):
print(a)
return a
testunit = unittest.TestSuite()
testunit.addTest(hell('ad'))
filename = '/Users/vivi/Downloads/aa.html'
fp = open(filename, 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'print', description=u'简单')
runner.run(testunit)
When I run it, I got this error:
Traceback (most recent call last):
File "/Applications/Python 3.5/……/B.py", line 30, in <module>
testunit.addTest(hell('ad'))
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/suite.py", line 47, in addTest
raise TypeError("{} is not callable".format(repr(test)))
TypeError: 'ad' is not callable
What should I do to make the script works?
You're adding the result of the call to hell('ad') to your tests, not the hell function itself. Since the hell function returns its argument, it returns the string ad, which is not a callable (function or the like).
Use
testunit.addTest(hell)
instead.
What about that argument then, how do you pass that?
Well, there are ways to do that, but generally, try not to let your unit test functions take an argument. Thus, hell() should siply not take an argument.
If you code your unit test correctly, you'll find that you rarely need to pass it an argument.
The line testunit.addTest(hell('ad')) doesn't do what you intend it to do. It doesn't tell the test suite to run hell('ad') later on. Rather, it calls that function immediately and passed the return value (which happens to be the string 'ad' you gave it as an argument) to addTest. That causes the exception later on, since a string is not a valid test case.
I'm not exactly sure how you should fix this. Your hell function doesn't appear to actually test anything, so there's not an obvious way to transform it into a proper test. There is a unittest.FunctionTestCase class that wraps a function up as a TestCase, but it doesn't appear to have any way of passing arguments to the function. Probably you should write a TestCase subclass class, and add various test_-prefixed methods that actually test things.
I had a right answer for my question, give the code:
# -*- coding: utf-8 -*-
import unittest
import HTMLTestRunner
def hell(a):
print(a)
return a
class HellTest(unittest.TestCase):
def setUp(self):
self.hell = hell
def tearDown(self):
pass
def testHell(self):
self.assertEqual(self.hell('aaa'), 'aaa')
if __name__ == '__main__':
testunit = unittest.TestSuite()
testunit.addTest(HellTest('testHell'))
filename = '/Users/vivi/Downloads/aa.html'
fp = open(filename, 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'不要生成error啦!', description=u'简单1112')
runner.run(testunit)
fp.close()
But, I do not know why add the class code 'class HellTest()'.The answer comes from a Chinese people whoes name is '幽谷奇峰'。Source code reference:https://segmentfault.com/q/1010000007427143?_ea=1345414

How to mock open differently depending on the parameters passed to open()

My question is how to mock open in python, such that it reacts differently depending on the argument open() is called with. These are some different scenario's that should be possible:
open a mocked file; read preset contents, the basic scenario.
open two mocked files and have them give back different values for the read() method. The order in which the files are opened/read from should not influence the results.
Furthermore, if I call open('actual_file.txt') to open an actual file, I want the actual file to be opened, and not a magic mock with mocked behavior. Or if I just don't want the access to a certain file mocked, but I do want other files to be mocked, this should be possible.
I know about this question: Python mock builtin 'open' in a class using two different files.
But that answer only partially answers up to the second requirement. The part about order independent results is not included and it does not specify how to mock only some calls, and allow other calls to go through to the actual files (default behavior).
A bit late, but I just recently happened upon the same need, so I'd like to share my solution, based upon this answer from the referred-to question:
import pytest
from unittest.mock import mock_open
from functools import partial
from pathlib import Path
mock_file_data = {
"file1.txt": "some text 1",
"file2.txt": "some text 2",
# ... and so on ...
}
do_not_mock: {
# If you need exact match (see note in mocked_file(),
# you should replace these with the correct Path() invocations
"notmocked1.txt",
"notmocked2.txt",
# ... and so on ...
}
# Ref: https://stackoverflow.com/a/38618056/149900
def mocked_file(m, fn, *args, **kwargs):
m.opened_file = Path(fn)
fn = Path(fn).name # If you need exact path match, remove this line
if fn in do_not_mock:
return open(fn, *args, **kwargs)
if fn not in mock_file_data:
raise FileNotFoundError
data = mock_file_data[fn]
file_obj = mock_open(read_data=data).return_value
file_obj.__iter__.return_value = data.splitlines(True)
return file_obj
def assert_opened(m, fn):
fn = Path(fn)
assert m.opened_file == fn
#pytest.fixture()
def mocked_open(mocker):
m = mocker.patch("builtins.open")
m.side_effect = partial(mocked_file, m)
m.assert_opened = partial(assert_opened, m)
return m
def test_something(mocked_open):
...
# Something that should NOT invoke open()
mocked_open.assert_not_called()
...
# Something that SHOULD invoke open()
mocked_open.assert_called_once()
mocked_open.assert_opened("file1.txt")
# Depends on how the tested unit handle "naked" filenames,
# you might have to change the arg to:
# Path.cwd() / "file1.txt"
# ... and so on ...
Do note that (1) I am using Python 3, and (2) I am using pytest.
This can be done by following the approach in the other question's accepted answer (Python mock builtin 'open' in a class using two different files) with a few alterations.
First off. Instead of just specifying a side_effect that can be popped. We need to make sure the side_effect can return the correct mocked_file depending on the parameters used with the open call.
Then if the file we wish to open is not among the files we wish to mock, we instead return the original open() of the file instead of any mocked behavior.
The code below demonstrates how this can be achieved in a clean, repeatable way. I for instance have this code inside of a file that provides some utility functions to make testing easier.
from mock import MagicMock
import __builtin__
from mock import patch
import sys
# Reference to the original open function.
g__test_utils__original_open = open
g__test_utils__file_spec = None
def create_file_mock(read_data):
# Create file_spec such as in mock.mock_open
global g__test_utils__file_spec
if g__test_utils__file_spec is None:
# set on first use
if sys.version_info[0] == 3:
import _io
g__test_utils__file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
else:
g__test_utils__file_spec = file
file_handle = MagicMock(spec=g__test_utils__file_spec)
file_handle.write.return_value = None
file_handle.__enter__.return_value = file_handle
file_handle.read.return_value = read_data
return file_handle
def flexible_mock_open(file_map):
def flexible_side_effect(file_name):
if file_name in file_map:
return file_map[file_name]
else:
global g__test_utils__original_open
return g__test_utils__original_open(file_name)
global g__test_utils__original_open
return_value = MagicMock(name='open', spec=g__test_utils__original_open)
return_value.side_effect = flexible_side_effect
return return_value
if __name__ == "__main__":
a_mock = create_file_mock(read_data="a mock - content")
b_mock = create_file_mock(read_data="b mock - different content")
mocked_files = {
'a' : a_mock,
'b' : b_mock,
}
with patch.object(__builtin__, 'open', flexible_mock_open(mocked_files)):
with open('a') as file_handle:
print file_handle.read() # prints a mock - content
with open('b') as file_handle:
print file_handle.read() # prints b mock - different content
with open('actual_file.txt') as file_handle:
print file_handle.read() # prints actual file contents
This borrows some code straight from the mock.py (python 2.7) for the creating of the file_spec.
side note: if there's any body that can help me in how to hide these globals if possible, that'd be very helpful.

How do I mock an open used in a with statement (using the Mock framework in Python)?

How do I test the following code with unittest.mock:
def testme(filepath):
with open(filepath) as f:
return f.read()
Python 3
Patch builtins.open and use mock_open, which is part of the mock framework. patch used as a context manager returns the object used to replace the patched one:
from unittest.mock import patch, mock_open
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
If you want to use patch as a decorator, using mock_open()'s result as the new= argument to patch can be a little bit weird. Instead, use patch's new_callable= argument and remember that every extra argument that patch doesn't use will be passed to the new_callable function, as described in the patch documentation:
patch() takes arbitrary keyword arguments. These will be passed to the Mock (or new_callable) on construction.
#patch("builtins.open", new_callable=mock_open, read_data="data")
def test_patch(mock_file):
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
Remember that in this case patch will pass the mocked object as an argument to your test function.
Python 2
You need to patch __builtin__.open instead of builtins.open and mock is not part of unittest, you need to pip install and import it separately:
from mock import patch, mock_open
with patch("__builtin__.open", mock_open(read_data="data")) as mock_file:
assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")
The way to do this has changed in mock 0.7.0 which finally supports mocking the python protocol methods (magic methods), particularly using the MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
An example of mocking open as a context manager (from the examples page in the mock documentation):
>>> open_name = '%s.open' % __name__
>>> with patch(open_name, create=True) as mock_open:
... mock_open.return_value = MagicMock(spec=file)
...
... with open('/some/path', 'w') as f:
... f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')
With the latest versions of mock, you can use the really useful mock_open helper:
mock_open(mock=None, read_data=None)
A helper function to create a
mock to replace the use of open. It works for open called directly or
used as a context manager.
The mock argument is the mock object to configure. If None (the
default) then a MagicMock will be created for you, with the API
limited to methods or attributes available on standard file handles.
read_data is a string for the read method of the file handle to
return. This is an empty string by default.
>>> from mock import mock_open, patch
>>> m = mock_open()
>>> with patch('{}.open'.format(__name__), m, create=True):
... with open('foo', 'w') as h:
... h.write('some stuff')
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
To use mock_open for a simple file read() (the original mock_open snippet already given on this page is geared more for write):
my_text = "some text to return when read() is called on the file object"
mocked_open_function = mock.mock_open(read_data=my_text)
with mock.patch("__builtin__.open", mocked_open_function):
with open("any_string") as f:
print f.read()
Note as per docs for mock_open, this is specifically for read(), so won't work with common patterns like for line in f, for example.
Uses python 2.6.6 / mock 1.0.1
The top answer is useful but I expanded on it a bit.
If you want to set the value of your file object (the f in as f) based on the arguments passed to open() here's one way to do it:
def save_arg_return_data(*args, **kwargs):
mm = MagicMock(spec=file)
mm.__enter__.return_value = do_something_with_data(*args, **kwargs)
return mm
m = MagicMock()
m.side_effect = save_arg_return_array_of_data
# if your open() call is in the file mymodule.animals
# use mymodule.animals as name_of_called_file
open_name = '%s.open' % name_of_called_file
with patch(open_name, m, create=True):
#do testing here
Basically, open() will return an object and with will call __enter__() on that object.
To mock properly, we must mock open() to return a mock object. That mock object should then mock the __enter__() call on it (MagicMock will do this for us) to return the mock data/file object we want (hence mm.__enter__.return_value). Doing this with 2 mocks the way above allows us to capture the arguments passed to open() and pass them to our do_something_with_data method.
I passed an entire mock file as a string to open() and my do_something_with_data looked like this:
def do_something_with_data(*args, **kwargs):
return args[0].split("\n")
This transforms the string into a list so you can do the following as you would with a normal file:
for line in file:
#do action
I might be a bit late to the game, but this worked for me when calling open in another module without having to create a new file.
test.py
import unittest
from mock import Mock, patch, mock_open
from MyObj import MyObj
class TestObj(unittest.TestCase):
open_ = mock_open()
with patch.object(__builtin__, "open", open_):
ref = MyObj()
ref.save("myfile.txt")
assert open_.call_args_list == [call("myfile.txt", "wb")]
MyObj.py
class MyObj(object):
def save(self, filename):
with open(filename, "wb") as f:
f.write("sample text")
By patching the open function inside the __builtin__ module to my mock_open(), I can mock writing to a file without creating one.
Note: If you are using a module that uses cython, or your program depends on cython in any way, you will need to import cython's __builtin__ module by including import __builtin__ at the top of your file. You will not be able to mock the universal __builtin__ if you are using cython.
If you don't need any file further, you can decorate the test method:
#patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"
To patch the built-in open() function with unittest:
This worked for a patch to read a json config.
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
The mocked object is the io.TextIOWrapper object returned by the open() function
#patch("<src.where.object.is.used>.open",
return_value=io.TextIOWrapper(io.BufferedReader(io.BytesIO(b'{"test_key": "test_value"}'))))
def test_object_function_under_test(self, mocker):
I'm using pytest in my case, and the good news is that in Python 3 the unittest library can also be imported and used without issue.
Here is my approach. First, I create a conftest.py file with reusable pytest fixture(s):
from functools import cache
from unittest.mock import MagicMock, mock_open
import pytest
from pytest_mock import MockerFixture
class FileMock(MagicMock):
def __init__(self, mocker: MagicMock = None, **kwargs):
super().__init__(**kwargs)
if mocker:
self.__dict__ = mocker.__dict__
# configure mock object to replace the use of open(...)
# note: this is useful in scenarios where data is written out
_ = mock_open(mock=self)
#property
def read_data(self):
return self.side_effect
#read_data.setter
def read_data(self, mock_data: str):
"""set mock data to be returned when `open(...).read()` is called."""
self.side_effect = mock_open(read_data=mock_data)
#property
#cache
def write_calls(self):
"""a list of calls made to `open().write(...)`"""
handle = self.return_value
write: MagicMock = handle.write
return write.call_args_list
#property
def write_lines(self) -> str:
"""a list of written lines (as a string)"""
return ''.join([c[0][0] for c in self.write_calls])
#pytest.fixture
def mock_file_open(mocker: MockerFixture) -> FileMock:
return FileMock(mocker.patch('builtins.open'))
Where I decided to make the read_data as a property, in order to be more pythonic. It can be assigned in a test function with whatever data that open() needs to return.
In my test file, named something like test_it_works.py, I have a following test case to confirm intended functionality:
from unittest.mock import call
def test_mock_file_open_and_read(mock_file_open):
mock_file_open.read_data = 'hello\nworld!'
with open('/my/file/here', 'r') as in_file:
assert in_file.readlines() == ['hello\n', 'world!']
mock_file_open.assert_called_with('/my/file/here', 'r')
def test_mock_file_open_and_write(mock_file_open):
with open('/out/file/here', 'w') as f:
f.write('hello\n')
f.write('world!\n')
f.write('--> testing 123 :-)')
mock_file_open.assert_called_with('/out/file/here', 'w')
assert call('world!\n') in mock_file_open.write_calls
assert mock_file_open.write_lines == """\
hello
world!
--> testing 123 :-)
""".rstrip()
Check out the gist here.
Sourced from a github snippet to patch read and write functionality in python.
The source link is over here
import configparser
import pytest
simpleconfig = """[section]\nkey = value\n\n"""
def test_monkeypatch_open_read(mockopen):
filename = 'somefile.txt'
mockopen.write(filename, simpleconfig)
parser = configparser.ConfigParser()
parser.read(filename)
assert parser.sections() == ['section']
def test_monkeypatch_open_write(mockopen):
parser = configparser.ConfigParser()
parser.add_section('section')
parser.set('section', 'key', 'value')
filename = 'somefile.txt'
parser.write(open(filename, 'wb'))
assert mockopen.read(filename) == simpleconfig
SIMPLE #patch with assert
If you're wanting to use #patch. The open() is called inside the handler and is read.
#patch("builtins.open", new_callable=mock_open, read_data="data")
def test_lambda_handler(self, mock_open_file):
lambda_handler(event, {})

Categories

Resources