Here's a generalized example of the code I'm trying to test using mock. I get an AttributeError.
Here's __init__.py:
import logging
log = logging.getLogger(__name__)
class SomeError(Exception):
pass
class Stuff(object):
# stub
def method_a(self):
try:
stuff = self.method_b()
except SomeError, e:
log.error(e)
# show a user friendly error message
return 'no ponies'
return 'omg ponies'
def method_b(self):
# can raise SomeError
return ''
In tests.py I have something like this:
import mock
import unittest
from package.errors import SomeError
from mypackage import Stuff
some_error_mock = mock.Mock()
some_error_mock.side_effect = SomeError
class MyTest(unittest.TestCase):
#mock.patch.object('Stuff', 'method_b', some_error_mock)
def test_some_error(self):
# assert that method_a handles SomeError correctly
mystuff = Stuff()
a = mystuff.method_a()
self.assertTrue(a == 'no ponies')
When running the test, mock raises AttributeError saying: "Stuff does not have the attribute 'method_b'"
What am I doing wrong here?
The decorator first argument should be the class of the object, you mistakenly used a string of the name of the class
#mock.patch.object('Stuff', 'method_b', some_error_mock)
should become
#mock.patch.object(Stuff, 'method_b', some_error_mock)
http://docs.python.org/dev/library/unittest.mock#patch-object
Related
I have a class which raises ValueError for a non-existent file path constructor parameter and I'd like to test it.
import os
import os.path
class MetricsManager:
def __init__(self, reference_file_path, translations_file_path):
if not os.path.isfile(reference_file_path):
raise ValueError(f'{reference_file_path} does not exist.')
if not os.path.isfile(translations_file_path):
raise ValueError(f'{translations_file_path} does not exist')
self._references_file_path = reference_file_path
self.translations_file_path = translations_file_path
But my test below fails.
import unittest
from src.metrics_manager import MetricsManager
def create_instance():
x = MetricsManager('foo.txt', 'bar.txt')
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertRaises(ValueError, create_instance())
if __name__ == '__main__':
unittest.main()
I did not have the assertRaises() call coded properly:
self.assertRaises(ValueError, MetricsManager, 'foo.txt', 'bar.txt')
assertRaises takes the function to be called as an argument, not the return value from actually calling it.
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertRaises(ValueError, create_instance)
If you call it first, the exception occurs before assertRaises gets called. You have to let assertRaises make the actual call.
You can also use assertRaises in a with statement:
class MyTestCase(unittest.TestCase):
def test_something(self):
with self.assertRaises(ValueError):
create_instance()
Here, you do call create_instance yourself, because any exception raised inside the with statement is implicitly caught and passed to the context manager's __exit__ method.
I'm trying to do a pytest on a function without return values in a class:
# app.py
from utils import DBConnection
class App:
def add_ticket_watcher(self, ticket_key, watcher_name):
if ticket_key is None:
raise ValueError('Ticket key is empty')
instance = DBConnection()
instance.save(ticket_key, watcher_name)
From above code, add_ticket_watcher() is a method which has no return statement. I learned from this article that we can use mocks to mimic the expected behavior of this method.
For mocking function with pytest, I found that we can use monkeypatch.
So, my approach is to perform 2 test cases for add_ticket_watcher():
test valid ticket key
test invalid ticket key
I have something like:
# test_app.py
import pytest
from src.app import App
#pytest.fixture
def app():
app = App()
return app
# Positive test case: Setup parameterize test for valid ticket key
add_valid_watcher_data = (
('func_name', 'ticket_key', 'watcher_name', 'expected_result', 'comment'),
[
('add_ticket_watcher', 'TIC-13527', 'someone', None, 'add watcher at valid ticket key'),
]
)
#pytest.mark.parametrize(*add_valid_watcher_data)
def test_add_ticket_watcher(monkeypatch, app, func_name, ticket_key, watcher_name, expected_result, comment):
def mock_add_ticket_watcher(ticket_key, watcher_name):
# Since `add_ticket_watcher()` has no return statement, I'm mocking the result as None
return None
monkeypatch.setattr(app, "add_ticket_watcher", mock_add_ticket_watcher)
# Run each input parameter from add_valid_watcher_data to `add_ticket_watcher()`
watcher = getattr(app, func_name)(ticket_key, watcher_name)
# If watcher has None value, then `add_ticket_watcher()` execution is successful.
assert expected_result == watcher
# Negative test case: Setup parameterize test for invalid ticket key
add_invalid_watcher_data = (
('func_name', 'ticket_key', 'watcher_name', 'exception_message', 'comment'),
[
('add_ticket_watcher', 'TIC-xx', 'someone', 'Ticket key is empty', 'add watcher at invalid ticket key'),
('add_ticket_watcher', None, 'someone', 'Ticket key is empty', 'ticket key has None value'),
]
)
#pytest.mark.parametrize(*add_invalid_watcher_data)
def test_exception_add_ticket_watcher(app, func_name, ticket_key, watcher_name, exception_message, comment):
with pytest.raises(ValueError, match=exception_message):
# Run each input parameter from add_invalid_watcher_data to `add_ticket_watcher()`
getattr(app, func_name)(ticket_key, watcher_name)
In test_add_ticket_watcher(), I'm not sure what to assert. However, since App.add_ticket_watcher(ticket_key, watcher_name) has no return statement. I create a mock function to return None.
Is there a better way to achieve the same purpose?
How to create unit test for methods without return statement in Pytest?
In addition to what chepner has mentioned. You can test if logger.info is being called using mock.
And in order to test the negative scenario, you can force it to raise exception using mock side_effects, and then you can test whether logger.exception is getting called.
I am not familiar with pytest but unittest. However, I would write these 3 tests for your function ( with a little modification to your existing function :))
Note: Test method names are descriptive, hence, I am not adding extra comments.
app.py
from utils import DBConnection
import cx_Oracle
class App:
def add_ticket_watcher(self, ticket_key, watcher_name):
if ticket_key is None:
raise ValueError('Ticket key is empty')
instance = DBConnection()
try:
instance.save(ticket_key, watcher_name)
except Exception as e:
raise cx_Oracle.DatabaseError('Database save failed')
test_app.py
import unittest
import app
import cx_Oracle
from mock import patch
class TestApp(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.app = app.App()
def test_if_exception_raised_when_ticket_value_is_none(self):
with self.assertRaises(ValueError):
self.app.add_ticket_watcher(None, 'dummyname')
def test_if_dbconnection_save_is_called(self):
with patch('app.DBConnection.save') as mock_dbconn_save:
self.app.add_ticket_watcher(123, 'dummyname')
self.assertTrue(mock_dbconn_save.called)
def test_if_dbconnection_save_raises_error(self):
with patch('app.DBConnection.save', side_effect = Exception) as mock_dbconn_save_exc:
with self.assertRaises(cx_Oracle.DatabaseError):
self.app.add_ticket_watcher(123, 'dummyname')
if __name__ == '__main__':
unittest.main(verbosity=2)
I have the following endpoint,
#developer_blueprint.route("/init_db", methods=["POST"])
def initialize_database():
try:
upload_data(current_app)
logger.debug("Database entries upload.")
return jsonify({"result": "Database entries uploaded."}), 201
except Exception as e:
return jsonify({"error": str(e)})
def upload_data(app):
with open("src/core/data/data.json") as data_file:
data = json.load(data_file)
try:
current_app.db.put(("somenamespace", "test", "default"), data, None)
except Exception as e:
raise e
I'm trying to figure out how to unit test this (we need to get coverage on our code).
Do I just mock up app.db? How can I do that?
Any suggestions would be appreciated.
It is not uncommon to mock database calls for unit testing using something like unittest.mock and then run Aerospike in a container or VM for end-to-end testing.
However, keep in mind that the Aerospike Python client library is written in C for better performance and thus it is not easy to do partial patching (aka "monkey patching"). For example, you will get a TypeError: can't set attributes of built-in/extension type if you try to simply patch out aerospike.Client.put.
One approach is to create a mock client object to replace or sub-class the Aerospike client object. The implementation of this mock object depends on your code and the cases you are testing for.
Take the following example code in which app.db is an instance of the Aerospike client library:
# example.py
import aerospike
import json
class App(object):
db = None
def __init__(self):
config = {'hosts': [('127.0.0.1', 3000)]}
self.db = aerospike.client(config).connect()
def upload_data(app):
with open("data.json") as data_file:
data = json.load(data_file)
try:
app.db.put(("ns1", "test", "default"), data, None)
except Exception as e:
raise e
if __name__ == "__main__":
app = App()
upload_data(app)
In writing unit tests for the upload_data function let's assume you want to test for a success case which is determined to mean that the put method is called and no exceptions are raised:
# test.py
from unittest import TestCase, main
from unittest.mock import PropertyMock, patch
from example import App, upload_data
from aerospike import Client, exception
class MockClient(Client):
def __init__(self, *args, **kwargs):
pass
def put(self, *args, **kwargs):
return 0
class ExampleTestCase(TestCase):
def test_upload_data_success(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = client = MockClient()
app = App()
with patch.object(client, 'put') as put_mock:
upload_data(app)
put_mock.assert_called()
if __name__ == '__main__':
main()
In the test_upload_data_success method the App.db property is patched with the MockClient class instead of the aerospike.Client class. The put method of the MockClient instance is also patched so that it can be asserted that the put method gets called after upload_data is called.
To test that an exception raised by the Aerospike client is re-raised from the upload_data function, the MockClient class can be modified to raise an exception explicitly:
# test.py
from unittest import TestCase, main
from unittest.mock import PropertyMock, patch
from example import App, upload_data
from aerospike import Client, exception
class MockClient(Client):
def __init__(self, *args, **kwargs):
self.put_err = None
if 'put_err' in kwargs:
self.put_err = kwargs['put_err']
def put(self, *args, **kwargs):
if self.put_err:
raise self.put_err
else:
return 0
class ExampleTestCase(TestCase):
def test_upload_data_success(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = client = MockClient()
app = App()
with patch.object(client, 'put') as put_mock:
upload_data(app)
put_mock.assert_called()
def test_upload_data_error(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = MockClient(put_err=exception.AerospikeError)
app = App()
with self.assertRaises(exception.AerospikeError):
upload_data(app)
if __name__ == '__main__':
main()
****** # I want to mock an exception beyond the scope of class Exception in Python. Below is my not-functional code. Please help identifying where's wrong. ******
from mock import patch
import unittest2
class A():
pass
class B(object):
def a(self):
return True
def b(self):
***# This function runs without problem and created an error outside of class Exception***
try:
raise A()
except Exception as ex:
print type(ex).__name__
except:
print 'error outside of Exception is captured.'
def c(self):
# This is for test
try:
self.a()
except Exception as ex:
print type(ex).__name__
except:
print 'error outside of Exception is captured.'
class Test(unittest2)
def raise_error(self):
raise A()
#patch('B.a')
def test_c(self, mock_a):
***# This does not function as B().b(), instead it throws an TypeError Exception. I need to mock an error not inherited from class Exception ***
mock_a.side_effect = self.raise_error
B().c()
Given this code:
try:
#do something
except IOError as message:
logging.error(message)
raise message
I want to test the exception handling part in order to have full coverage.
In the unittest I've tried with:
with patch(new=Mock(side_effect=IOError(errno.EIO))):
self.assertRaises(IOError)
but it doesnt work.
Is this approach correct?
Actually you need to start the Mock so that the side_effectstarts, for example the following:
class Test(unittest.TestCase):
def test(self):
mock = m.Mock()
mock.side_effect = Exception("Big badaboum")
self.assertRaises(Exception, mock)
self.assertRaises can take a callable as second argument, making it equivalent to:
class Test(unittest.TestCase):
def test(self):
mock = m.Mock()
mock.side_effect = Exception("Big badaboum")
with self.assertRaises(Exception):
mock()
And if you want to use it in a test with patch, you can do the following:
import unittest.mock as m
import unittest
def raise_error():
try:
print("Hello") #placeholder for the try clause
except Exception as e:
print(e) #placeholder for the exceptclause
class Test(unittest.TestCase):
#m.patch("__main__.raise_error", side_effect=Exception("Big badaboum")) #replace __main__ by the name of the module with your function
def test(self, mock):
with self.assertRaises(Exception):
mock()
unittest.main()
Edit: And to test the raise of an error inside an except block you need to mock a function call inside the try block you wrote, for instance:
import unittest.mock as m
import unittest
def do_sthing():
print("Hello")
def raise_error():
try:
do_sthing() #this call can be mocked to raise an IOError
except IOError as e:
print(e.strerror)
raise ValueError("Another one")
class Test(unittest.TestCase):
def test(self):
with m.patch("__main__.do_sthing", side_effect=IOError("IOError")):
self.assertRaises(ValueError, raise_error)
unittest.main()
You can use the decorator syntax as well (just putting the test above rewritten to spare some CPU cycle):
class Test(unittest.TestCase):
#m.patch("__main__.do_sthing",side_effect=IOError("IOError"))
def test(self, mock):
self.assertRaises(ValueError, raise_error)