Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 2.7 mock/patch: understanding assert_called_XYZ()

I'm relatively new to Python and unit testing in Python. From the Java world I know the concept of mocking but it seem to be much different from what I can see in Python.

I found this guide, which I found very helpful: http://www.voidspace.org.uk/python/mock/index.html

But as I wrote my (a bit more complex) tests with mocked out dependencies I noticed a strage behavior. I decided to create a reduced, simple example which also does not work as I expect it.

Take a look at this, the result and my expectation I have added as comments:

import unittest
from mock import patch, Mock, MagicMock

class BasicTest(unittest.TestCase):

    @patch("StringIO.StringIO")
    def testSomethingNotWorkingAsExpected(self, StringIOMock):
        StringIOMock.assert_called_once() # asserts, but why?

    @patch("StringIO.StringIO")
    def testSomethingSomehowWorking(self, StringIOMock):
        # self.instantiateStringIO() # intentionally commented out
        assert StringIOMock.called # does not assert (leading to failure of this test); as expected. If the above line is not commented, this asserts as expected.

    def instantiateStringIO(self):
        import StringIO
        StringIO.StringIO()

Why is assert_called_once() asserting the instantiation of StringIO even it has not been instantiated yet? And why does assert ClassMock.called bring the expected results?

Using assert not ... to assert a method has not been called I found here: Assert a function/method was not called using Mock. I inverted this pattern in my case by omitting the not.

Somewhere I found the pattern ClassMock.return_value to reference an instance. But I understand this as a way to manupulate the instance of a Mock before it will be called, not as a way to access the instance that might an underliing code have internally created. Or am I wrong?

My environment:

  • Python 2.7.3
  • mock 0.8.8
  • Fedora 19

Probably my understanding of the mock/patch thing is wrong. Could please someone aditionally explain what a class mock does and how it works?

Edit1: Added output

... and added paraphrase in parens to comment in testSomethingSomehowWorking

This is the output:

.F
======================================================================
FAIL: testSomethingSomehowWorking (test_test.BasicTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/mock.py", line 1224, in patched
    return func(*args, **keywargs)
  File "test_test.py", line 15, in testSomethingSomehowWorking
    assert StringIOMock.called # does not assert; as expected
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
like image 304
try-catch-finally Avatar asked Dec 05 '13 11:12

try-catch-finally


People also ask

What does mock mock () do?

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.

Should I use mock MagicMock?

Mock vs. 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.

What is the difference between mock and MagicMock?

With Mock you can mock magic methods but you have to define them. MagicMock has "default implementations of most of the magic methods.". If you don't need to test any magic methods, Mock is adequate and doesn't bring a lot of extraneous things into your tests.

What is Side_effect in mock Python?

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.


1 Answers

The method assert_called_once does not exist and it does not perform an assertion. It's no different from writing StringIOMock.assert_foo_bar_does_not_exist() or any other method. The mock library doesn't check whether the method called on the mock actually exists.

If you use assert_called_once_with then it fails as expected.

You can use the spec parameter to raise an error when you call a non-existent method:

@patch("StringIO.StringIO", spec=StringIO.StringIO)
def testSomethingNotWorkingAsExpected(self, StringIOMock):
    StringIOMock.assert_called_once() # will fail as the method doesn't exist
like image 183
Simeon Visser Avatar answered Nov 05 '22 22:11

Simeon Visser