Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does unittest.mock fail when the production class constructor takes extra arguments?

I have run into a problem which I think might be a bug with the libraries I am using. However, I am fairly new to python, unittest, and unittest.mock libraries so this may just be a hole in my understanding.

While adding tests to some production code I have run into an error, I have produced a minimal sample that reproduces the issue:

import unittest
import mock

class noCtorArg:
    def __init__(self):
        pass
    def okFunc(self):
        raise NotImplemented


class withCtorArg:
    def __init__(self,obj):
        pass
    def notOkFunc(self):
        raise NotImplemented
    def okWithArgFunc(self, anArgForMe):
        raise NotImplemented

class BasicTestSuite(unittest.TestCase):
    """Basic test Cases."""

    # passes
    def test_noCtorArg_okFunc(self):
        mockSUT = mock.MagicMock(spec=noCtorArg)
        mockSUT.okFunc()
        mockSUT.assert_has_calls([mock.call.okFunc()])

    # passes
    def test_withCtorArg_okWithArgFuncTest(self):
        mockSUT = mock.MagicMock(spec=withCtorArg)
        mockSUT.okWithArgFunc("testing")
        mockSUT.assert_has_calls([mock.call.okWithArgFunc("testing")])

    # fails
    def test_withCtorArg_doNotOkFuncTest(self):
        mockSUT = mock.MagicMock(spec=withCtorArg)
        mockSUT.notOkFunc()
        mockSUT.assert_has_calls([mock.call.notOkFunc()])


if __name__ == '__main__':
    unittest.main()

How I run the tests and the output is as follows:

E:\work>python -m unittest testCopyFuncWithMock
.F.
======================================================================
FAIL: test_withCtorArg_doNotOkFuncTest (testCopyFuncWithMock.BasicTestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "testCopyFuncWithMock.py", line 38, in test_withCtorArg_doNotOkFuncTest
    mockSUT.assert_has_calls([mock.call.notOkFunc()])
  File "C:\Python27\lib\site-packages\mock\mock.py", line 969, in assert_has_calls
    ), cause)
  File "C:\Python27\lib\site-packages\six.py", line 718, in raise_from
    raise value
AssertionError: Calls not found.
Expected: [call.notOkFunc()]
Actual: [call.notOkFunc()]

----------------------------------------------------------------------
Ran 3 tests in 0.004s

FAILED (failures=1)

I am using python 2.7.11, with mock version 2.0.0 installed via pip.

Any suggestions for what I am doing wrong? Or does this look like a bug in the library?

like image 965
stk_sfr Avatar asked Jun 20 '16 12:06

stk_sfr


People also ask

How does Unittest mock work?

unittest. 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.

What is the difference between mock and MagicMock?

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.

Is Pytest better than Unittest?

Which is better – pytest or unittest? Although both the frameworks are great for performing testing in python, pytest is easier to work with. The code in pytest is simple, compact, and efficient. For unittest, we will have to import modules, create a class and define the testing functions within that class.

What is MagicMock?

MagicMock. MagicMock objects provide a simple mocking interface that allows you to set the return value or other behavior of the function or object creation call that you patched. This allows you to fully define the behavior of the call and avoid creating real objects, which can be onerous.


1 Answers

Interestingly, the way you chose to perform the assert has masked your issue.

Try, instead of this:

mockSUT.assert_has_calls(calls=[mock.call.notOkFunc()])

to do this:

mockSUT.assert_has_calls(calls=[mock.call.notOkFunc()], any_order=True)

You'll see the actual exception:

TypeError("'obj' parameter lacking default value")

This is because you tried to instantiate an instance of the class withCtorArg that has the parameter obj with no default value. If you had tried to actually instantiate it directly, you would've seen:

TypeError: __init__() takes exactly 2 arguments (1 given)

However, since you let the mock library handle the instantiation of a mock object, the error happens there - and you get the TypeError exception.

Modifying the relevant class:

class withCtorArg:
    def __init__(self, obj = None):
        pass
    def notOkFunc(self):
        pass
    def okWithArgFunc(self, anArgForMe):
        pass

and adding a default None value for obj solves the issue.

like image 72
advance512 Avatar answered Sep 25 '22 18:09

advance512