Python mock a dependancy object - python

I have a python class (MyClass) I am unit testing. It has a external dependency which creates a database, I would like to mock that object and check it was called.
from entities_database import DBCreator
class MyClass():
def __init__(self):
self.db = DBCreator()
def create(self, name):
value = self.db.create_db(name)
I would like to test the init function, so see that a mock DBCreator called. And then to test the create function to check that create_db() was called with "name", and it returned "name" to value.
I am not sure how to go about this, so far I have this:
from entities_database import DBCreator
from test_unit import MyClass
import unittest
import mock
class MyClassTest(unittest.TestCase):
#mock.patch('entities_database.DBCreator', autospec=True)
def test_myclass_init(self, dbcreator_mock):
creator = mock.create_autospec(DBCreator)
dbcreator_mock.return_value = creator
myclass = MyClass()
assert(dbcreator_mock.called)
Which results in:
F
======================================================================
FAIL: test_myclass_init (unit_test.MyClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/paul/Data/TSC/Code/parltrack_eu/venv/lib/python3.6/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/Users/paul/Data/TSC/Code/parltrack_eu/unit_test.py", line 32, in test_myclass_init
assert(dbcreator_mock.called)
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.036s
FAILED (failures=1)
How can I fix my code?
---- UPDATE ----
Apart from the solution below with dependency injection, one can also do the following with patch, as suggested below #Goyo
from entities_database import DBCreator
from test_unit import MyClass
import unittest
import mock
class MyClassTest(unittest.TestCase):
#mock.patch('test_unit.DBCreator', autospec=True)
def test_myclass_init(self, mock_db):
'''
Test the initialization of MyClass
Test that DBCreator is initialized
'''
creator = mock.create_autospec(DBCreator)
mock_db.return_value = creator
# Assert that the DBCreator is initialised
myclass = MyClass()
assert(mock_db.called)
#mock.patch('test_unit.DBCreator', autospec=True)
def test_myclass_create(self, mock_db):
'''
Test the myclass.create() function
and assert it calls db.create_db() with the correct
argument
'''
name = 'unittest'
myclass = MyClass()
myclass.create(name)
# Assert that create_db was called with name
myclass.db.create_db.assert_called_with(name)

Patching is tricky. You have to patch the object in the same namespace where the SUT will look it up. In this case probably you want #mock.patch('test_unit.DBCreator', autospec=True).
Using dependency injection avoids this kind of problems and makes things more explicit and clear:
class MyClass():
def __init__(self, db):
self.db = db
def create(self, name):
value = self.db.create_db(name)
Then in your test:
class MyClassTest(unittest.TestCase):
def test_myclass_init(self):
db = mock.Mock()
myclass = MyClass(db)
self.assertEqual(myclass.db, db)
def test_myclass_create(self):
db = mock.Mock()
myclass = MyClass(db)
name = mock.Mock()
myclass.create(name)
myclass.db.create_db.assert_called_once_with(name)

I am not able to try with your example code. But you should try to mock create_db function not DBCreator class.

Related

How to patch a module method that is called within a class?

I have the following structure:
# create.py
import sshHandler
class Create:
def __init__(self):
self.value = sshHandler.some_method()
# sshHandler.py
def some_method():
return True
If I kow try to patch sshHandler.some_method it will not work as expected
from unittest import TestCase
from unittest.mock import patch
import create
class TestCreate(TestCase):
#patch("sshHandler.some_method")
def test_create(self, mock_ssh):
mock_ssh.return_value = False
c = create.Create()
# c.value = True but should be false
The result I am looking for is that some_method would be patched in create as well (and return false). If I just call some_method in the context of test_create it works as expected. How do I fix the patch so that it is also active in the Create class when accessing sshHandler?
I saw this question Why python mock patch doesn't work?, but couldn't solve my problem with the information given there.
You've patched the wrong module. Instead patch the sshHandler.some_method patch create.sshHandler.some_method. You must patch the object of module you're handling.

StopIteration when mocking base class of own class

In a rather complex test scenario I need to mock the base class of one of my own classes and instantiate the latter several times. When I do that my test errors with a StopIteration exception. Here's what my scenario boils down to in this respect:
Code under test (my_class.py):
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
class MySession(OAuth2Session):
pass
class MyClass:
def init(self, x):
self.x = x
client = BackendApplicationClient(client_id=x)
self.session = MySession(client=client)
Test code (test_mock.py):
import unittest
from unittest.mock import patch
with patch('requests_oauthlib.OAuth2Session') as MockSession:
from my_class import MyClass
cls = MyClass()
class MockTest(unittest.TestCase):
def test_mock_1(self):
cls.init(1)
self.assertIsNotNone(cls.session)
def test_mock_2(self):
cls.init(2)
self.assertIsNotNone(cls.session)
Test result:
$ python -m unittest test_mock
.E
======================================================================
ERROR: test_mock_2 (test_mock.MockTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "...\test_mock.py", line 16, in test_mock_2
cls.init(2)
File "...\my_class.py", line 11, in init
self.session = MySession(client=client)
File "C:\Python39\lib\unittest\mock.py", line 1093, in __call__
return self._mock_call(*args, **kwargs)
File "C:\Python39\lib\unittest\mock.py", line 1097, in _mock_call
return self._execute_mock_call(*args, **kwargs)
File "C:\Python39\lib\unittest\mock.py", line 1154, in _execute_mock_call
result = next(effect)
StopIteration
----------------------------------------------------------------------
Ran 2 tests in 0.003s
FAILED (errors=1)
I have debugged into the unittest.mock.MagicMock class but I can't figure out what's going on. In MagicMock's _execute_mock_call() method I noticed that self.side_effect is a tuple iterator object and when next() is called on that in the second test (test_mock_2) it results in the StopIteration.
Both tests run "OK" if I don't use the MySession subclass, i.e. self.session = OAuth2Session(client=client) in MyClass' init() method. (But that's just not how the real code under test works...)
Any ideas anyone?
You should mock class which you directly use, because your custom class inherit Mock and next starts unexpected behavior.
Rewrite path method to your custom class.
import unittest
from unittest.mock import patch
with patch('my_class.MySession') as MockSession:
from my_class import MyClass

Mocking a property call returning MagicMock, not value

I have the following configuration class:
class ConfigB(object):
Id = None
def __Init__(self, Id):
self.Id = Id
Which is instantiated in the following class and the property printed:
from config.ConfigB import ConfigB
class FileRunner(object):
def runProcess(self, Id)
cfgB = ConfigB(Id)
print(cfgB.Id)
I have created the following test class to test it, where I am trying to mock both the instantiation and the cfgB.Id property call:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
#mock.patch('ConfigB.ConfigB.__init__')
#mock.patch('ConfigB.ConfigB.Id')
def test_methodscalled(self, cfgBId, cfgBInit):
fileRunner = FileRunner()
cfgBId.return_value = 17
cfgBInit.return_value = None
print(cfgBId)
fileRunner.runProcess(1)
Note the print(cfgBId) statement before fileRunner is called. I get the following output:
<MagicMock name='Id' id='157297352'>
<MagicMock name='Id' id='157297352'>
For some reason when I set the return value here in the test class:
cfgBId.return_value = 17
That is not getting called on the line in the FileRunner() class:
print(cfgB.Id)
What do I need to do to properly get my configuration property to display?
Also note that my 'ConfigB' class instantiation is much more complicated than displayed above which is why I want to patch the instantiation and the call to the Id property.
*Update: I have changed my class as suggested by #mgilson but it is still not working:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
#mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
fileRunner = FileRunner()
cfgB.Id = 17
print(cfgBId)
fileRunner.runProcess(1)
I am now getting the following output from the two print statements:
<MagicMock name='ConfigB' id='157793640'>
<MagicMock name='ConfigB().Id' id='157020512'>
Any ideas why the above isn't working?
*Solution:
I was able to get it to work after a minor change to the test method that #mgilson suggested:
import unittest
from unittest.mock import MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
#mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, configB):
fileRunner = FileRunner()
cfgB = MagicMock()
cfgB.Id = 17
#This will return the cfgB MagicMock when `ConfigB(Id)` is called in `FileRunner` class
configB.return_value = cfgB
print(cfgB.Id)
fileRunner.runProcess(1)
#To test whether `ConfigB(17)` was called
configB.assert_called_with(17)
I now get the following outputs:
<MagicMock name='ConfigB' id='157147936'>
17
It looks to me like it would be better to just replace the entire ConfigB object in the FileRunner namespace. Then your test looks something like this:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
#mock.patch('FileRunner.ConfigB')
def test_methodscalled(self, cfgB):
fileRunner = FileRunner()
cfgB.return_value.Id = 17
fileRunner.runProcess(1)
Note that #mock.patch('FileRunner.ConfigB') is going to replace the ConfigB class in the FileRunner namespace with a mock. You can then configure the mock to do whatever you like -- e.g. have an Id that equals 17.
To mock Id property, you can patch the class with a Mock instantiated with the property like so:
#mock.patch('ConfigB.ConfigB', Mock(Id='17'))
def test_methodscalled(self, cfgB):
cfgB.return_value.__init__.return_value = None # FWIW, this isn't necessary
Recently I've had slightly more complicated problem which I solved in similar way by setting return_value for mock used in mock.patch (shown below).
Your solution can be further refactored to give you more control over used objects by using mock.patch as context manager (Ctrl-F "with patch" in linked section) instead as decorator:
class TestFileRunner(unittest.TestCase):
def test_methodscalled(self):
id = 17
with mock.patch('FileRunner.ConfigB', return_value=Mock(Id=id)) as configB:
FileRunner().runProcess(1)
configB.assert_called_with(id)
My case: asserting that call was made by using patched object instance.
I wanted to test conditional logging in some class (code examples simplified). Tested module:
# tested_module.py
import logging
from exceptions import CustomExceptionClass
class ClassUnderTest:
logger = logging.getLogger(__name__)
def tested_instance_method(self, arguments):
... # Some more code.
if 2 in arguments:
self.logger.exception(CustomExceptionClass(arguments))
... # Some more code.
Working tests (after solving the problem):
# test_module.py
from unittest import mock
import tested_module
class TestModule:
def test_exception_logged(self):
method_arguments = 1, 2, 3
logger_mock = mock.Mock()
tested_class_instance = mock.Mock(logger=logger_mock)
exception_mock_instance = mock.Mock()
with mock.patch.object(tested_module, 'CustomExceptionClass',
return_value=exception_mock_instance) as exception_mock:
tested_module.ClassUnderTest.tested_instance_method(tested_class_instance,
method_arguments)
logger_mock.exception.assert_called_once()
self.assertSequenceEqual(logger_mock.exception.call_args_list,
[mock.call(exception_mock_instance)])
exception_mock.assert_called_once()
self.assertSequenceEqual(exception_mock.call_args_list,
[mock.call(method_arguments)])
The problematic part, before I came up with solution above was this:
# Check below fails.
self.assertSequenceEqual(logger_mock.exception.call_args_list,
[mock.call(exception_mock)]) # Wrong object, "()" problem.
Then I tried that:
self.assertSequenceEqual(logger_mock.exception.call_args_list,
[mock.call(exception_mock())]) # Can't do `exception_mock()` ...
# ... because it was called 2nd time and assertion below will fail.
exception_mock.assert_called_once()
The issue was that I had to somehow get that different mock object, which was returned when exception_mock was called and without calling exception_mock 2nd time. So creating mock.Mock instance up front and assigning to return_value was the right answer.

Mock entire python class

I'm trying to make a simple test in python, but I'm not able to figure it out how to accomplish the mocking process.
This is the class and def code:
class FileRemoveOp(...)
#apply_defaults
def __init__(
self,
source_conn_keys,
source_conn_id='conn_default',
*args, **kwargs):
super(v4FileRemoveOperator, self).__init__(*args, **kwargs)
self.source_conn_keys = source_conn_keys
self.source_conn_id = source_conn_id
def execute (self, context)
source_conn = Connection(conn_id)
try:
for source_conn_key in self.source_keys:
if not source_conn.check_for_key(source_conn_key):
logging.info("The source key does not exist")
source_conn.remove_file(source_conn_key,'')
finally:
logging.info("Remove operation successful.")
And this is my test for the execute function:
#mock.patch('main.Connection')
def test_remove_execute(self,MockConn):
mock_coon = MockConn.return_value
mock_coon.value = #I'm not sure what to put here#
remove_operator = FileRemoveOp(...)
remove_operator.execute(self)
Since the execute method try to make a connection, I need to mock that, I don't want to make a real connection, just return something mock. How can I make that? I'm used to do testing in Java but I never did on python..
First it is very important to understand that you always need to Mock where it the thing you are trying to mock out is used as stated in the unittest.mock documentation.
The basic principle is that you patch where an object is looked up,
which is not necessarily the same place as where it is defined.
Next what you would need to do is to return a MagicMock instance as return_value of the patched object. So to summarize this you would need to use the following sequence.
Patch Object
prepare MagicMock to be used
return the MagicMock we've just created as return_value
Here a quick example of a project.
connection.py (Class we would like to Mock)
class Connection(object):
def execute(self):
return "Connection to server made"
file.py (Where the Class is used)
from project.connection import Connection
class FileRemoveOp(object):
def __init__(self, foo):
self.foo = foo
def execute(self):
conn = Connection()
result = conn.execute()
return result
tests/test_file.py
import unittest
from unittest.mock import patch, MagicMock
from project.file import FileRemoveOp
class TestFileRemoveOp(unittest.TestCase):
def setUp(self):
self.fileremoveop = FileRemoveOp('foobar')
#patch('project.file.Connection')
def test_execute(self, connection_mock):
# Create a new MagickMock instance which will be the
# `return_value` of our patched object
connection_instance = MagicMock()
connection_instance.execute.return_value = "testing"
# Return the above created `connection_instance`
connection_mock.return_value = connection_instance
result = self.fileremoveop.execute()
expected = "testing"
self.assertEqual(result, expected)
def test_not_mocked(self):
# No mocking involved will execute the `Connection.execute` method
result = self.fileremoveop.execute()
expected = "Connection to server made"
self.assertEqual(result, expected)
I found that this simple solution works in python3: you can substitute a whole class before it is being imported for the first time. Say I have to mock class 'Manager' from real.manager
class MockManager:
...
import real.manager
real.manager.Manager = MockManager
It is possible to do this substitution in init.py if there is no better place.
It may work in python2 too but I did not check.

How do I mock a superclass's __init__ create an attribute containing a mock object for a unit test?

I am attempting to write a unit test for a class's __init__:
def __init__(self, buildNum, configFile = "configfile.txt"):
super(DevBuild, self).__init__(buildNum, configFile)
if configFile == "configfile.txt":
self.config.MakeDevBuild()
The config attribute is set by the super's __init__. I'm using mock, and I want the config attribute to be a mock object. However, I haven't been able to figure out how to actually make that happen. Here's the best I could come up with for the test:
def test_init(self):
with patch('DevBuild.super', create=True) as mock_super:
mock_MakeDevBuild = MagicMock()
mock_super.return_value.config.MakeDevBuild = mock_MakeDevBuild
# Test with manual configuration
self.testBuild = DevBuild("42", "devconfigfile.txt")
self.assertFalse(mock_MakeDevBuild.called)
# Test with automated configuration
self.testBuild = DevBuild("42")
mock_MakeDevBuild.assert_called_once_with()
However, this doesn't work--I get an error:
Error
Traceback (most recent call last):
File "/Users/khagler/Projects/BuildClass/BuildClass/test_devBuild.py", line 17, in test_init
self.testBuild = DevBuild("42")
File "/Users/khagler/Projects/BuildClass/BuildClass/DevBuild.py", line 39, in __init__
self.config.MakeDevBuild()
AttributeError: 'DevBuild' object has no attribute 'config'
Clearly I'm not setting the config attribute correctly, but I have no idea where exactly I should be setting it. Or for that matter, if what I want to do is even possible. Can anyone tell me what I need to do to make this work?
You can't mock __init__ by setting it directly - see _unsupported_magics in mock.py.
As for what you can do, you can mock __init__ by passing it to patch, like so:
mock_makeDevBuild = MagicMock()
def mock_init(self, buildNum, configFile):
self.config = MagicMock()
self.config.MakeDevBuild = mock_makeDevBuild
with patch('DevBuild.SuperDevBuild.__init__', new=mock_init):
DevBuild("42")
mock_makeDevBuild.assert_called_once_with()
where SuperDevBuild is a base class of DevBuild.
If you really want to mock super(), you can perhaps make a class and then bind __init__ to object manually, like
mock_makeDevBuild = MagicMock()
def get_mock_super(tp, obj):
class mock_super(object):
#staticmethod
def __init__(buildNum, configFile):
obj.config = MagicMock()
obj.config.MakeDevBuild = mock_makeDevBuild
return mock_super
with patch('DevBuild.super', create=True, new=get_mock_super):
DevBuild("42")
mock_makeDevBuild.assert_called_once_with()
which works, but is quite ugly..
I do it this way, mocking the inherited class init:
from unittest import mock
#mock.patch.object(HierarchicalConf, "__init__")
def test_super_init(self, mock_super_init):
# act
ConfigurationService('my_args')
# assert
mock_super_init.assert_called_once_with(args)
Given the class:
class ConfigurationService(HierarchicalConf):
def __init__(self, dag_name) -> None:
"""Wrapper of Hierarchical Conf."""
# ... my code
super().__init__(args)
And if you want to also mock the ConfigurationService init, you can do quite the same:
#mock.patch.object(ConfigurationService, "__init__")
def test_init(self, mock_init):
# act
ConfigurationService('my_args')
# assert
mock_init.assert_called_once_with('my_args')

Categories