I have this sort of setup where I'm testing a class which is using another class, and I want to mock the latter so I'm only testing the first class itself.
nuclear_reactor.py
:
class NuclearReactor():
def __init__(self):
print "initializing the nuclear reactor"
def start(self):
print "starting the nuclear reactor"
nuclear_manager.py
:
from nuclear_reactor import NuclearReactor
class NuclearManager():
def __init__(self):
print "manager creating the nuclear reactor"
self.reactor = NuclearReactor()
def start(self):
print "manager starting the nuclear reactor"
self.reactor.start()
test_nuclear_manager.py
:
from mock import Mock
import nuclear_manager
from nuclear_manager import NuclearManager
def test():
mock_reactor = nuclear_manager.NuclearReactor = Mock()
nuke = NuclearManager()
nuke.start()
nuke.start()
print mock_reactor.mock_calls
print mock_reactor.start.call_count
test()
What I'd like to test is that NuclearReactor.start
is called,
but when I run this I get:
manager creating the nuclear reactor
manager starting the nuclear reactor
manager starting the nuclear reactor
[call(), call().start(), call().start()]
0
Which I totally understand since start
is an attribute of the instance and not of the class, and I could parse the mock_calls
, but isn't there a better way to check that the call of an instantiated mocked class is made?
I could use dependency injection in NuclearManager
to pass a mock NuclearReactor
, but I'm thinking there would be an alternative way using just mock.
Mock Testing provides you the ability to isolate and test your code without any interference of the dependencies and other variables like network issues and traffic fluctuations. In simple words, in mock testing, we replace the dependent objects with mock objects.
Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls. Through mocking you can explicitly define the return value of methods without actually executing the steps of the method.
So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.
How do we mock in Python? Mocking in Python is done by using patch to hijack an API function or object creation call. When patch intercepts a call, it returns a MagicMock object by default. By setting properties on the MagicMock object, you can mock the API call to return any value you want or raise an Exception .
You are indeed testing if start
has been called directly on the class, which your code does not. You can test the method on the instance directly; remember that an instance is produced by calling the class:
print mock_reactor.return_value.calls
print mock_reactor.return_value.start.call_count
The Mock.return_value
attribute is the result of the call to the mocked class, so the instance.
You can also just call the mock. Mocks by default always return the exact same object when called, a new mock representing that return value:
print mock_reactor().calls
print mock_reactor().start.call_count
The result of calling a mock instance, and the mock instance return_value
attribute, are one and the same.
You were already on the right path by printing out the calls to the NuclearReactor
mock, you just missed the detail that start()
was invoked on the called mock, so call().start()
, not start()
was recorded.
You may want to use mock.patch()
to handle the patching, rather than by direct assignment; this makes sure that the patch is removed again so that other tests can make their own decisions on what is mocked:
import mock
from nuclear_manager import NuclearManager
@mock.patch('nuclear_manager.NuclearReactor')
def test(mock_reactor):
nuke = NuclearManager()
nuke.start()
nuke.start()
instance = mock_reactor.return_value
assert instance.start.call_count == 2
instance.assert_called()
I used it as a decorator here; when the test()
function is called, the mock is put in place, and when the function exits, it is removed again. You can also use patch()
as a context manager to limit the scope of the patch even more finely.
Also, for unit testing like this, do use the unittest
library:
import mock
import unittest
import nuclear_manager
class NuclearManagerTests(unittest.TestCase):
@mock.patch('nuclear_manager.NuclearReactor')
def test_start(self, mock_reactor):
nuke = NuclearManager()
nuke.start()
nuke.start()
instance = mock_reactor.return_value
self.assertEqual(instance.start.call_count, 2)
instance.assert_called()
if __name__ == '__main__':
unittest.main()
This lets you fit your tests into a larger test suite, enable and disable tests, and integrate with other testing tools.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With