16

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.

3
  • Why do you expect .start() to be called once when you call the manager twice? Commented Dec 2, 2017 at 10:30
  • Yes, you should inject an instance into NuclearManager because your code now makes the manager set its reactor instance to a default value. Only the highest level parts of a program should have defaults. Commented Dec 2, 2017 at 10:32
  • @quamrana: whether or not the OP should use dependency injection is an entirely separate discussion. Not all code under test will be suitable for dependency injection. Commented Dec 2, 2017 at 11:01

2 Answers 2

18

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Not that I use patch myself, but its good to know that @patch() will restore the scope later.
Thanks a lot Marijn, makes sense. Just a note that I'm using pytest so no need for the unittest extra baggage. The assert is all I need.
-2

The way I use mocks is like this: (Code is Python 3)

from unittest.mock import MagicMock class NuclearManager(): def __init__(self, reactor): print("manager creating the nuclear reactor") self.reactor = reactor def start(self): print("manager starting the nuclear reactor") self.reactor.start() def test(): mock_reactor = MagicMock() nuke = NuclearManager(mock_reactor) nuke.start() nuke.start() # These two prints would actually be asserts of some sort print(mock_reactor.mock_calls) print(mock_reactor.start.call_count) test() 

Output:

manager creating the nuclear reactor manager starting the nuclear reactor manager starting the nuclear reactor [call.start(), call.start()] 2 

1 Comment

The OP has a hypothetical situation. Not all code requires injection, you can trivially still reach the instance, because that's a call to the class.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.