Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing that a method in instance has been called in mock

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.

like image 804
sagism Avatar asked Dec 02 '17 10:12

sagism


People also ask

What is a mocking testing?

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.

What is mocking a method?

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.

What is the difference between MagicMock and mock?

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 you mock a method in Python?

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 .


1 Answers

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.

like image 133
Martijn Pieters Avatar answered Oct 21 '22 07:10

Martijn Pieters