18

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)
Community
  • 1
  • 1
try-catch-finally
  • 7,436
  • 6
  • 46
  • 67

1 Answers1

14

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
Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
  • 1
    Thank you for clarifying that undefined methods called on a Mock will be ignored (calling undefined methods on normal instances usually lead to an error). I suspected this, but were unable to find it. I somewhere found `assert_called_once()` because I was looking for a assertion method that ignores the parameters. It was obviously incorrect. Thanks for solving this. – try-catch-finally Dec 05 '13 at 12:40