I am patching a method for my TestCase class
#patch(
"my_function_to_mock"
side_effect=new_behaviour_function,
)
class MyTestCase(unittests.TestCase):
def test_my_code():
assert True
however unit_test fails because of unexpected number of argument
Traceback (most recent call last):
File "/usr/local/lib/python3.8/unittest/mock.py", line 1325, in patched
return func(*newargs, **newkeywargs)
TypeError: test_my_code() takes 1 positional argument but 2 were given
Everything seems logical, test_my_code was called with my mock as an argument, this failed because my unit test signature do not except a mock.
How can I apply a patch to my TestCase without having to change all my unit tests signatures? I do not need to access mock inside my test method.
I could solve my problem by manually starting a patch at TestCase class setup.
#classmethod
def setUpClass(cls):
my_patch = patch(
"my_function_to_mock",
side_effect=new_behaviour_function,
)
my_patch.start()
cls.addClassCleanup(my_patch.stop)
super(TestCase, cls).setUpClass()
Related
I am trying to use patch() to inject mock into a call after reading this tutorial: https://kimsereylam.com/python/2021/03/19/how-to-use-patch-in-python-unittest.html, but the expect mock ID is different than actual.
Here is snippet of code to illustrate:
my_class.py
class MyClass:
def __init__(self):
self.value = 5
run.py
from my_class import MyClass
def call_first():
o = MyClass()
print(o)
call_second(o)
def call_second(myclass):
print("second is called")
print(myclass)
main.py
from unittest.mock import patch
from my_class import MyClass
from run import call_first, call_second
with patch("run.MyClass") as myclass_mock:
print(myclass_mock)
print(call_first())
with patch("run.call_second") as call_second_mock:
call_second_mock.assert_called_once_with(myclass_mock)
The output is:
<MagicMock name='MyClass' id='140711608367712'>
<MagicMock name='MyClass()' id='140711598743312'>
second is called
<MagicMock name='MyClass()' id='140711598743312'>
None
Traceback (most recent call last):
File "main.py", line 9, in <module>
call_second_mock.assert_called_once_with(myclass_mock)
File "/usr/lib/python3.8/unittest/mock.py", line 924, in assert_called_once_with
raise AssertionError(msg)
AssertionError: Expected 'call_second' to be called once. Called 0 times.
What did I miss? I am trying to use patch() so I can mock the MyClass() object that is instantiated inside call_first() and passed to call_second().
with patch("run.MyClass") as myclass_mock:
with patch("run.call_second") as call_second_mock:
#print(myclass_mock)
instance = myclass_mock.return_value
print(instance)
print(call_first())
call_second_mock.assert_called_once_with(instance)
I need to use .return_value to get the MagicMock for the class instance
I should mock the call_second first
Background
In python mocking, there is a function assert_has_calls which can verify the order of arguments used to call a function. It has a helper object call which helps validate the arguments.
Problem
Using call(), you cannot pass self as a kwarg. It gives the error: TypeError: __call__() got multiple values for argument 'self'. Why? The workaround is to simply not use self as a kwarg, but I'm afraid there's something fundamental I'm misunderstanding and can't find anything in the documentation that points to it.
Live Code
The code below is pasted here for you to play with: https://repl.it/#artoonie/ObviousIntentReality#main.py
Pasted Code
File 1: test_class.py
needs to be a separate file for mock.patch decorator discoverability
class Class:
def func(self, num):
return num
class Runner:
def run(self):
return Class().func(1)
File 2: test.py
import mock
import test_class
unmockedFunction = test_class.Class.func
class Test:
#mock.patch('test_class.Class.func', autospec=True)
def test_me(self, mocker):
# Make the mock do nothing
mocker.side_effect = unmockedFunction
# Run the function
test_class.Runner().run()
# This works
mocker.assert_called_with(self=mock.ANY, num=1)
# This works
mocker.assert_has_calls([
mock.call(mock.ANY, num=1)
])
# This fails
mocker.assert_has_calls([
mock.call(self=mock.ANY, num=1)
])
Test().test_me()
Similar to a setUp() method, is there a way to define in one place how a certain exception is handled for all the tests in a given TestCase test class?
My use-case: the stack trace of mock/patch's assert_any_call only gives the expected call it could not find, but I want to add the actual calls against the mock. Each unit test can add this info to the stack trace in an except clause, but I want it defined in one place to avoid bloated code.
As #Jérôme pointed out, this can be achieved by creating a decorator to wrap your tests (e.g. https://stackoverflow.com/revisions/43938162/1)
Here is the code I ended up using:
import mock
from unittest import TestCase
from foo.bar import method_that_calls_my_class_method
def _add_mock_context(test_func):
"""This decorator is only meant for use in the MyClass class for help debugging test failures.
It adds to the stack trace the context of what actual calls were made against the method_mock,
without bloating the tests with boilerplate code."""
def test_wrapper(self, *args, **kwargs):
try:
test_func(self, *args, **kwargs)
except AssertionError as e:
# Append the actual calls (mock's exception only includes expected calls) for debugging
raise type(e)('{}\n\nActual calls to my_method mock were:\n{}'.format(e.message, self.method_mock.call_args_list))
return test_wrapper
class TestMyStuff(TestCase):
def setUp(self):
class_patch = mock.patch('mine.MyClass', autospec=True)
self.addCleanup(class_patch.stop)
class_mock = class_patch.start()
self.method_mock = class_mock.return_value.my_method
#_add_mock_context
def test_my_method(self):
method_that_calls_my_class_method()
self.method_mock.assert_any_call(arg1='a', arg2='b')
self.method_mock.assert_any_call(arg1='c', arg2='d')
self.method_mock.assert_any_call(arg1='e', arg2='f')
self.assertEqual(self.method_mock.call_count, 3)
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?
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.
I don't think I can explain definitively why this is the case, I still suspect a bug in the Mock library, since the issue only occurs for a test case with no arguments on the called function. Thanks to advance512 for pointing out that the real error was hidden!
However to work around this issue, without having to modify the production code I am going to use the following approach:
# passes
#mock.patch ('mymodule.noCtorArg')
def test_noCtorArg_okFunc(self, noCtorArgMock):
mockSUT = noCtorArg.return_value
mockSUT.okFunc()
mockSUT.assert_has_calls([mock.call.okFunc()])
# passes
#mock.patch ('mymodule.withCtorArg')
def test_withCtorArg_okWithArgFuncTest(self, withCtorArgMock):
mockSUT = withCtorArg.return_value
mockSUT.okWithArgFunc("testing")
mockSUT.assert_has_calls([mock.call.okWithArgFunc("testing")])
# now passes
#mock.patch ('mymodule.withCtorArg')
def test_withCtorArg_doNotOkFuncTest(self, withCtorArgMock):
mockSUT = withCtorArg.return_value
mockSUT.notOkFunc()
mockSUT.assert_has_calls([mock.call.notOkFunc()], any_order=True)
Edit:
One problem with this is that the mock does not have spec set. This means that the SUT is able to call methods that do not exist on the original class definition.
An alternative approach is to wrap the class to be mocked:
class withCtorArg:
def __init__(self,obj):
pass
def notOkFunc(self):
raise NotImplemented
def okWithArgFunc(self, anArgForMe):
raise NotImplemented
class wrapped_withCtorArg(withCtorArg):
def __init__(self):
super(None)
class BasicTestSuite(unittest.TestCase):
"""Basic test Cases."""
# now passes
def test_withCtorArg_doNotOkFuncTest(self):
mockSUT = mock.MagicMock(spec=wrapped_withCtorArg)
mockSUT.notOkFunc()
#mockSUT.doesntExist() #causes the test to fail. "Mock object has no attribute 'doesntExist'"
assert isinstance (mockSUT, withCtorArg)
mockSUT.assert_has_calls([mock.call.notOkFunc()], any_order=True)
I'm doing some tests. A bunch of my test functions have common setups, so I decided I should use #with_setup decorator from nose.tools. I've simplified my problem down to this:
from nose.tools import with_setup
class TestFooClass(unittest.TestCase):
def setup_foo_value(self):
self.foo = 'foobar'
#with_setup(setup_foo_value)
def test_something(self):
print self.foo
I get the following error:
$ python manage.py test tests/test_baz.py
E
======================================================================
ERROR: test_something (project.tests.test_baz.TestFooClass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/user/Coding/project-backend/project/../project/tests/test_baz.py", line 17, in test_something
print self.foo
AttributeError: 'TestFooClass' object has no attribute 'foo'
----------------------------------------------------------------------
It's like setup_foo_value is not being run at all. Any help would be really appreciated!
According to the doc:
writing tests: "Please note that method generators are not supported in unittest.TestCase subclasses"
testing tools: "with_setup is useful only for test functions, not for test methods or inside of TestCase subclasses"
So you can either move your test method into a function, or add a setUp method to your class.
Try this modification. It worked for me.
from nose.tools import with_setup
def setup_foo_value(self):
self.foo = 'foobar'
#with_setup(setup_foo_value)
def test_something(self):
print self.foo
The initial idea can be implemented in the following way
import wrapt
#wrapt.decorator
def setup_foo_value(wrapped, instance, args, kwargs):
instance.foo = 'foobar'
return wrapped(*args, **kwargs)
class TestFooClass(unittest.TestCase):
#setup_foo_value
def test_something(self):
print self.foo
Important that It additionally uses wrapt Python module