I have an entry point function call it main
on an object that I would like to remain unmocked, since it calls several other methods on the object:
class Thing(object):
def main(self):
self.alpha()
self.bravo()
def alpha(self):
self.charlie()
def bravo(self):
raise TypeError("Requires Internet connection!")
def charlie(self):
raise Exception("Bad stuff happens here!")
This is pretty straight forward to mock manually:
thing = Thing()
thing.alpha = MagicMock()
thing.bravo = MagicMock()
And I can test to make sure that alpha and bravo are both called once, I can set side effects in alpha and bravo to make sure they're handled, etc. etc.
What I'm worried about is if the code definition changes and someone adds a charlie
call to main
. It's not mocked, so now the side effects will be felt (and they're things like writing to a file, connecting to a database, fetching stuff from the Internet, so that simple exception is not going to alert me that the tests are now bad).
My plan was to verify that my mock object calls no other methods than the ones I say it should (or raise a test exception). However if I do something like this:
MockThing = create_autospec(Thing)
thing = Thing()
thing.main()
print thing.method_calls
# [calls.main()]
Then main
is also mocked so it calls no other methods. How can I mock every method but the main method? (I'd like method_calls to be [calls.alpha(), calls.bravo()]
).
Edit: For Hacking Answers
Well, I have a really hacky solution, but I hope there is a better answer than this. Basically I rebind the method from the original class (Python bind an unbound method)
MockThing = create_autospec(Thing)
thing = MockThing()
thing.main = Thing.ingest.__get__(thing, Thing)
thing.main()
print thing.method_calls
# [calls.alpha(), calls.bravo()]
But there has to be a simpler solution than using function descriptors!
mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.
side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.
When I did these strange kind of stuff like call a real method of a class that I would mock I used to call the method static reference:
mt = Mock(Thing)
Thing.main(mt)
print(mt.mock_calls)
[call.alpha(), call.bravo()]
Anyway after write your test it is better to separate by use some collaborators to separate what you should mock from what you want to test: use these tests to lead the production code refactor and finally refactor your test to remove these kind of dirty tests.
I had the same issue, but I figured out a way to do it that I'm happy with. The example below uses your Thing class as listed above:
import mock
mock_thing = mock.create_autospec(Thing)
mock_thing.main = lambda x: Thing.main(mock_thing, x)
This will result in mock_thing calling an actual 'main' function belonging to mock_thing, but mock_thing.alpha() and mock_thing.beta() will both be called as mocks! (replace x with any parameters you're passing in to the function).
Hopefully that works for you!
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