I'm studying how to use mocking in my unit test program.
Now I have a SafeConfigParser object and I want to test what I write is correct.
After google the mocking usage of SafeConfigParser, I already know how to test the read of SafeConfigParser. But I still don't know how to verify the write of SafeConfigParser.
My idea is:
Make a empty buffer.
Consider a method that can set the buffer to SafeConfigParser.
Call the function which include SafeConfigParser.write()
Verify the buffer with my answer.
My program which need to be tested is like following:
def write_tokens_to_config(self):
"""Write token values to the config
"""
parser = SafeConfigParser()
with open(self.CONFIG_PATH) as fp:
parser.readfp(fp)
if not parser.has_section('Token'):
parser.add_section('Token')
parser.set('Token', 'access_token', self._access_token)
parser.set('Token', 'refresh_token', self._refresh_token)
with open(self.CONFIG_PATH, 'wb') as fp:
parser.write(fp)
P.S. You can check the read part from this url: http://www.snip2code.com/Snippet/4347/
I finally find out a solution :).
I modify my program(ex: program.py) to the followings:
class Program():
def __init__(self):
self._access_token = None
self._refresh_token = None
self.CONFIG_PATH = 'test.conf'
def write_tokens_to_config(self):
"""Write token value to the config
"""
parser = SafeConfigParser()
parser.read(self.CONFIG_PATH)
if not parser.has_section('Token'):
parser.add_section('Token')
parser.set('Token', 'access_token', self._access_token)
parser.set('Token', 'refresh_token', self._refresh_token)
with open(self.CONFIG_PATH, 'wb') as f:
parser.write(f)
And my test program like this:
class TestMyProgram(unittest.TestCase):
def setUp(self):
from program import Program
self.program = Program()
def test_write_tokens_to_config(self):
from mock import mock_open
from mock import call
self.program._access_token = 'aaa'
self.program._refresh_token = 'bbb'
with mock.patch('program.ConfigParser.SafeConfigParser.read'):
m = mock_open()
with mock.patch('__builtin__.open', m, create=True):
self.program.write_tokens_to_config()
m.assert_called_once_with(self.program.CONFIG_PATH, 'wb')
handle = m()
handle.write.assert_has_calls(
[
call('[Token]\n'),
call('access_token = aaa\n'),
call('refresh_token = bbb\n'),
]
)
Ref: http://docs.python.org/dev/library/unittest.mock#mock-open
Related
I use pathlib.Path.open() and pathlib.Path.unlink() in my productive code. The unittest for that work. But I use two different ways to patch(). One with #patch decorator and one with a context manager with mock.patch().
I would like to have #patch only like this.
class MyTest(unittest.TestCase):
#mock.patch('pathlib.Path.unlink')
#mock.patch('pathlib.Path.open')
def test_foobar(self, mock_open, mock_unlink):
But the real code currenty looks like this
import unittest
from unittest import mock
import pathlib
class MyTest(unittest.TestCase):
#mock.patch('pathlib.Path.unlink')
def test_foobar(self, mock_unlink):
# simulated CSV file
opener = mock.mock_open(read_data='A;B\n1;2')
with mock.patch('pathlib.Path.open', opener):
result = validate_csv(file_path=pathlib.Path('foo.csv'))
self.assertTrue(result)
Technical my problem here is that I do not know how to add my CSV content to mock_open when using the #patch decorator.
It could look like this:
class MyTest(unittest.TestCase):
#mock.patch('pathlip.Path.open')
#mock.patch('pathlib.Path.unlink')
def test_foobar(self, mymock_unlink, mymock_open):
# simulated CSV file
opener = mock.mock_open(read_data='A;B\n1;2')
# QUESTION: How do I bring 'opener' and 'mymock_open'
# together now?
result = validate_csv(file_path=pathlib.Path('foo.csv'))
self.assertTrue(result)
But the goal of my question is to improve readability and maintainability of the code. Using two decorators would reduce the indention. Choosing one way (decorators or context managers) would IMHO be easier to read.
For learning purposes
Q: How do I bring 'opener' and 'mymock_open' together now?
A: Assign side_effect and return_value of mymock_open to those of opener.
#mock.patch('pathlib.Path.open')
#mock.patch('pathlib.Path.unlink')
def test_foobar(self, mymock_unlink, mymock_open):
# simulated CSV file
opener = mock.mock_open(read_data='A;B\n1;2')
# QUESTION: How do I bring 'opener' and 'mymock_open'
# together now?
mymock_open.side_effect = opener.side_effect # +
mymock_open.return_value = opener.return_value # +
result = validate_csv(file_path=pathlib.Path('foo.csv'))
opener.assert_not_called() # +
mymock_open.assert_called_once() # +
mymock_unlink.assert_called_once() # +
self.assertTrue(result)
But this is hardly a readability improvement.
Both using decorators
#mock.patch('pathlib.Path.open', new_callable=lambda: mock.mock_open(read_data='A;B\n1;2')) # +
#mock.patch('pathlib.Path.unlink')
def test_foobar(self, mock_unlink, mock_open):
result = validate_csv(file_path=pathlib.Path('foo.csv'))
mock_open.assert_called_once() # +
mock_unlink.assert_called_once() # +
self.assertTrue(result)
Passing just mock.mock_open(read_data='A;B\n1;2') (as positional argument new) instead of new_callable=lambda: ... works too, but then #mock.patch won't pass mock_open to test_foobar.
Both using context managers
def test_foobar(self):
# simulated CSV file
opener = mock.mock_open(read_data='A;B\n1;2')
with mock.patch('pathlib.Path.unlink') as mock_unlink,\
mock.patch('pathlib.Path.open', opener) as mock_open: # +
self.assertIs(mock_open, opener) # +
result = validate_csv(file_path=pathlib.Path('foo.csv'))
mock_open.assert_called_once() # +
mock_unlink.assert_called_once() # +
self.assertTrue(result)
Notice that mock_open is the same instance as opener.
Verifying the solutions
Sample implementation of validate_csv for a minimal, reproducible example:
def validate_csv(file_path):
"""
:param pathlib.Path file_path:
:rtype: bool
"""
with file_path.open() as f:
data = f.read()
file_path.unlink()
return data == 'A;B\n1;2'
Brand new to this library
Here is the call stack of my mocked object
[call(),
call('test'),
call().instance('test'),
call().instance().database('test'),
call().instance().database().snapshot(),
call().instance().database().snapshot().__enter__(),
call().instance().database().snapshot().__enter__().execute_sql('SELECT * FROM users'),
call().instance().database().snapshot().__exit__(None, None, None),
call().instance().database().snapshot().__enter__().execute_sql().__iter__()]
Here is the code I have used
#mock.patch('testmodule.Client')
def test_read_with_query(self, mock_client):
mock = mock_client()
pipeline = TestPipeline()
records = pipeline | ReadFromSpanner(TEST_PROJECT_ID, TEST_INSTANCE_ID, self.database_id).with_query('SELECT * FROM users')
pipeline.run()
print mock_client.mock_calls
exit()
I want to mock this whole stack that eventually it gives me some fake data which I will provide as a return value.
The code being tested is
spanner_client = Client(self.project_id)
instance = spanner_client.instance(self.instance_id)
database = instance.database(self.database_id)
with database.snapshot() as snapshot:
results = snapshot.execute_sql(self.query)
So my requirements is that the results variable should contain the data I will provide.
How can I provide a return value to such a nested calls
Thanks
Create separate MagicMock instances for the instance, database and snapshot objects in the code under test. Use return_value to configure the return values of each method. Here is an example. I simplified the method under test to just be a free standing function called mut.
# test_module.py : the module under test
class Client:
pass
def mut(project_id, instance_id, database_id, query):
spanner_client = Client(project_id)
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
with database.snapshot() as snapshot:
results = snapshot.execute_sql(query)
return results
# test code (pytest)
from unittest.mock import MagicMock
from unittest import mock
from test_module import mut
#mock.patch('test_module.Client')
def test_read_with_query(mock_client_class):
mock_client = MagicMock()
mock_instance = MagicMock()
mock_database = MagicMock()
mock_snapshot = MagicMock()
expected = 'fake query results'
mock_client_class.return_value = mock_client
mock_client.instance.return_value = mock_instance
mock_instance.database.return_value = mock_database
mock_database.snapshot.return_value = mock_snapshot
mock_snapshot.execute_sql.return_value = expected
mock_snapshot.__enter__.return_value = mock_snapshot
observed = mut(29, 42, 77, 'select *')
mock_client_class.assert_called_once_with(29)
mock_client.instance.assert_called_once_with(42)
mock_instance.database.assert_called_once_with(77)
mock_database.snapshot.assert_called_once_with()
mock_snapshot.__enter__.assert_called_once_with()
mock_snapshot.execute_sql.assert_called_once_with('select *')
assert observed == expected
This test is kind of portly. Consider breaking it apart by using a fixture and a before function that sets up the mocks.
Either set the value directly to your Mock instance (those enters and exit should have not seen) with:
mock.return_value.instance.return_value.database.return_value.snapshot.return_value.execute_sql.return_value = MY_MOCKED_DATA
or patch and set return_value to target method, something like:
#mock.patch('database_engine.execute_sql', return_value=MY_MOCKED_DATA)
Is there a way to make a function that makes other functions to be called later named after the variables passed in?
For the example let's pretend https://example.com/engine_list returns this xml file, when I call it in get_search_engine_xml
<engines>
<engine address="https://www.google.com/">Google</engine>
<engine address="https://www.bing.com/">Bing</engine>
<engine address="https://duckduckgo.com/">DuckDuckGo</engine>
</engines>
And here's my code:
import re
import requests
import xml.etree.ElementTree as ET
base_url = 'https://example.com'
def make_safe(s):
s = re.sub(r"[^\w\s]", '', s)
s = re.sub(r"\s+", '_', s)
s = str(s)
return s
# This is what I'm trying to figure out how to do correctly, create a function
# named after the engine returned in get_search_engine_xml(), to be called later
def create_get_engine_function(function_name, address):
def function_name():
r = requests.get(address)
return function_name
def get_search_engine_xml():
url = base_url + '/engine_list'
r = requests.get(url)
engines_list = str(r.content)
engines_root = ET.fromstring(engines_list)
for child in engines_root:
engine_name = child.text.lower()
engine_name = make_safe(engine_name)
engine_address = child.attrib['address']
create_get_engine_function(engine_name, engine_address)
## Runs without error.
get_search_engine_xml()
## But if I try to call one of the functions.
google()
I get the following error.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'google' is not defined
Defining engine_name and engine_address seems to be working when I log it out. So I'm pretty sure the problem lies in create_get_engine_function, which admittedly I don't know what I'm doing and I was trying to piece together from similar questions.
Can you name a function created by another function with an argument that's passed in? Is there a better way to do this?
You can assign them to globals()
def create_get_engine_function(function_name, address):
def function():
r = requests.get(address)
function.__name__ = function_name
function.__qualname__ = function_name # for Python 3.3+
globals()[function_name] = function
Although, depending on what you're actually trying to accomplish, a better design would be to store all the engine names/addresses in a dictionary and access them as needed:
# You should probably should rename this to 'parse_engines_from_xml'
def get_search_engine_xml():
...
search_engines = {} # maps names to addresses
for child in engines_root:
...
search_engines[engine_name] = engine_address
return search_engines
engines = get_search_engine_xml()
e = requests.get(engines['google'])
<do whatever>
e = requests.get(engines['bing'])
<do whatever>
I am trying to mock some 3rd part library on setup so i can pretend it works as expected on my code.
I was able to mock it locally on which i configure all the returns on the function itself
class MockConnecton:
def __init__(self):
self._ch = Mock()
def channel(self):
return self._ch
class QEmiterTest(unittest.TestCase):
#patch('task_queues.queue.pika.BlockingConnection')
#patch('task_queues.queue.pika.ConnectionParameters')
def test_emiter(self,mock_params,mock_block):
config = {
'host':'mq',
'exchange':'test'
}
params = {"FOO":"BAR"}
mock_params.return_value = params
conn = MockConnecton()
mock_conn = Mock(wraps=conn)
mock_block.return_value = mock_conn
emitter = QEmitter(config['host'],config['exchange'])
mock_params.assert_called_with(config['host'])
mock_block.assert_called_with(params)
mock_conn.channel.assert_called_with()
conn._ch.exchange_declare.assert_called_with(exchange=config['exchange'],type='topic')
But when i try to move from this approach to a cleaner one with the mock start/stop i receive an error on the assertion:
AttributeError: '_patch' object has no attribute 'assert_called_with'
I am trying to port it like this
class QEmiterTest(unittest.TestCase):
def setUp(self):
mock_params = patch('task_queues.queue.pika.ConnectionParameters')
mock_block = patch('task_queues.queue.pika.BlockingConnection')
self.params_ret = {"FOO":"BAR"}
mock_params.return_value = self.params_ret
conn = MockConnecton()
self.mock_conn = Mock(wraps=conn)
mock_block.return_value = self.mock_conn
self.patch_params = mock_params
self.patch_block = mock_block
self.patch_params.start()
self.patch_block.start()
def test_emiter(self):
config = {
'host':'mq',
'exchange':'test'
}
emitter = QEmitter(config['host'],config['exchange'])
self.patch_params.assert_called_with(config['host'])
self.patch_block.assert_called_with(self.params_ret)
self.mock_conn.channel.assert_called_with()
self.mock_conn._ch.exchange_declare.assert_called_with(exchange=config['exchange'],type='topic')
def tearDown(self):
self.patch_params.stop()
self.patch_block.stop()
I may not fully understand the start and stop, i was under the assumpton that on setup it would apply the patch and by it's reference i would be able to make assertions . I also welcome any suggestons on how to make several mocks cleaner
patch object are patch and not mock. mock framework have two main duties:
Mock object used to record the call and follow your screenplay
Patch methods and object used to replace reference by something that you can use to sensor or simulate some behaviors
A lot of time we can use patch to install mocks... but patch are not mock.
patch.start() return the new reference (often a mock) used to patch the original one.
def setUp(self):
self.params_ret = {"FOO":"BAR"}
self.mock_conn = Mock(wraps=conn)
self.patch_params = patch('task_queues.queue.pika.ConnectionParameters', return_value = self.params_ret)
self.patch_block = patch('task_queues.queue.pika.BlockingConnection', return_value=self.mock_conn)
mock_params = self.patch_params.start()
mock_block = self.patch_block.start()
I want to test appendRole which is called getFileAsJson to read file with open.
My problem is that I don't know which open will be next. There are many if/elif.
def appendRole(self, hosts=None, _newrole=None, newSubroles=None, undoRole=False, config_path=None):
""" Same as changeRole but keeps subroles """
if hosts is None:
hosts = ["127.0.0.1"]
if newSubroles is None:
newSubroles = {}
if config_path is None:
config_path = self.config_path
with self._lock:
default = {}
data = self.getFileAsJson(config_path, default)
...................
...................
...................
...................
data1 = self.getFileAsJson(self.config_path_all, {"some"})
data2 = self.getFileAsJson(self.config_path_core, {"something"})
...................
...................
...................
def getFileAsJson(self, config_path, init_value):
"""
read file and return json data
if it wasn't create. Will created.
"""
self.createFile(config_path, init_value)
try:
with open(config_path, "r") as json_data:
data = json.load(json_data)
return data
except Exception as e:
self.logAndRaiseValueError(
"Can't read data from %s because %s" % (config_path, e))
Even you can find an answer to your question at Python mock builtin 'open' in a class using two different files I would like encourage you to change your approach to write tests for getFileAsJson() and then trust it.
To test appendRole() use mock.patch to patch getFileAsJson(), then by side_effect attribute you can instruct the mock to return exactly what you need for your test.
So, after some test on getFileAsJson() where you can use mock_open() to mock open builtin (maybe you need to patch createFile() too). Your appendRole()'s test looks like something like this:
#mock.patch('mymodule.getFileAsJson', autospec=True)
def test_appendRole(self, mock_getFileAsJson)
mock_getFileAsJson.side_effect = [m_data, m_data1,m_data2,...]
# where m_data, m_data1,m_data2, ... is what is supposed
# getFileAsJson return in your test
# Invoke appendRole() to test it
appendRole(bla, bla)
# Now you can use mock_getFileAsJson.assert* family methods to
# check how your appendRole call it.
# Moreover add what you need to test in appendRole()