I am having trouble mocking a function that accepts **kwargs. The scenario is I have ClassA (which in my specific case is something I didnt write) and it has a function with **kwargs. ClassB which has an instance of ClassA, and calls the **kwargs function. I want to test ClassB by mocking the call to Class A's function.
Here is what I have tried so far, and in both of my attempts I ended up with a TypeError. Is there any way to do this? Should I rethink another aspect of this?
import unittest
#a class i have no control over. Has a function accepting **kwargs
class ClassA(object):
def classFunctionAcceptingKwargs(self, **kwargs):
return kwargs["a"] + kwargs["b"]
# a mock of the above class
class Mock_ClassA(object):
def __init__(self):
self.mockclassFunctionAcceptingKwargs = lambda **kwargs: None
def classFunctionAcceptingKwargs(self, **kwargs):
#FAILS: TypeError: mockFunctionAcceptingKwargs() takes exactly 0 arguments (1 given)
return self.mockclassFunctionAcceptingKwargs(kwargs)
#ALSO FAILS: TypeError: mockFunctionAcceptingKwargs() argument after ** must be a mapping, not set
#return self.mockclassFunctionAcceptingKwargs(**{kwargs["a"] + kwargs["b"]})
#class B calls the class A kwargs but exposes a function with a dict
class ClassB(object):
def __init__(self, classA):
self.classA = classA
def doSomething(self, dict):
return self.classA.classFunctionAcceptingKwargs(**dict)
class TestClassA(unittest.TestCase):
def runTest(self):
a = ClassA()
result = a.classFunctionAcceptingKwargs(**{"a":1, "b": 2})
self.assertEqual(result, 3)
class TestClassB(unittest.TestCase):
def runTest(self):
mock = Mock_ClassA()
def mockFunctionAcceptingKwargs(**kwargs):
self.assertEqual(kwargs["a"], 1)
self.assertEqual(kwargs["b"], 2)
mock.mockclassFunctionAcceptingKwargs = mockFunctionAcceptingKwargs
b = ClassB(mock)
b.doSomething({"a": 1, "b": 2})
Stack trace:
Test Name: TestClassB
Test Outcome: Failed
Result StandardError:
======================================================================
ERROR: runTest (module1.TestClassB)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\test\module1.py", line 49, in runTest
b.doSomething({"a": 1, "b": 2})
File "C:\test\module1.py", line 28, in doSomething
return self.classA.classFunctionAcceptingKwargs(**dict)
File "C:\test\module1.py", line 18, in classFunctionAcceptingKwargs
return self.mockclassFunctionAcceptingKwargs(kwargs)
TypeError: mockFunctionAcceptingKwargs() takes exactly 0 arguments (1 given)
----------------------------------------------------------------------
Ran 1 test in 12.873s
FAILED (errors=1)
I'm not sure why you are calling another function. But if you must have an instance attribute mockclassFunctionAcceptingKwargs
function, then just pass on the kwargs
dictionary with **kwargs
:
class Mock_ClassA(object):
# ...
def classFunctionAcceptingKwargs(self, **kwargs):
return self.mockclassFunctionAcceptingKwargs(**kwargs)
You don't need to call that lambda at all if all you need is for classFunctionAcceptingKwargs
to exist:
class Mock_ClassA(object):
def __init__(self, mock_result):
self.mock_result = mock_result
def classFunctionAcceptingKwargs(self, **kwargs):
self.called_with = kwargs
return self.mock_result
then just pass in the mocked return value, whatever you need to be passed back to ClassB
for the test, and afterwards you can check that the right value was passed in too:
mock = Mock_ClassA(3) # to return 3 back to the caller
b = ClassB(mock)
b.doSomething({"a": 1, "b": 2})
self.assertEqual(mock.called_with, {'a': 1, 'b': 2})
You may want to use the unittest.mock
library to build a mock object to pass in (available in Python 3, and a backport for Python 2 can be installed). It'll let you create a mocked ClassA
and then use the API to test if the mock was used in the expected manner:
try:
# Python 3
from unittest import mock
except ImportError:
# Python 2, backport
import mock
class TestClassB(unittest.TestCase):
def runTest(self):
mockA = mock.Mock(spec=ClassA) # only accept attributes ClassA also has
mockA.classFunctionAcceptingKwargs.return_value = 3 # or whatever else you want it to return
b = ClassB(mockA)
b.doSomething({"a": 1, "b": 2})
mockA.classFunctionAcceptingKwargs.assert_called_once_with(a=1, b=2)
Demo with unittest.mock
as the mocking layer:
>>> from unittest import mock
>>> class ClassA(object):
... def classFunctionAcceptingKwargs(self, **kwargs):
... return kwargs["a"] + kwargs["b"]
...
>>> class ClassB(object):
... def __init__(self, classA):
... self.classA = classA
... def doSomething(self, dict):
... return self.classA.classFunctionAcceptingKwargs(**dict)
...
>>> mockA = mock.Mock(spec=ClassA)
>>> mockA.classFunctionAcceptingKwargs.return_value = 3
>>> b = ClassB(mockA)
>>> b.doSomething({"a": 1, "b": 2})
3
>>> mockA.classFunctionAcceptingKwargs.assert_called_once_with(a=1, b=2) # passes, no exception raised
>>> mockA.mock_calls
[call.classFunctionAcceptingKwargs(a=1, b=2)]