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
Related
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()
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
I am writing a unit test and trying to set up the test data to use for the test cases in the same python script.
However, When I run the script, it does create the test data, but prints an error message that the data do not exist, leading to the test failure. It's only when I run the script again that the test succeeds.
Below is a simplified script that I wrote to figure out what's going on.
import unittest
from ddt import ddt, file_data
import pandas
#ddt
class TestWhatever(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.setup_test_data()
print("setUpClass is running")
#classmethod
def tearDownClass(cls):
print("tearDownClass is running")
#classmethod
def setup_test_data(cls):
data = pandas.DataFrame({'msg':["testing"]})
data = data.transpose()
with open("practice_test.json", "w") as file:
file.write(data.to_json())
print("setup_test_data is running")
#file_data("practice_test.json")
def test_whatever_possible(self, msg):
print("test_whatever_possible is running :", msg)
self.assertEqual('q', 'q')
def test_whatever_impossible(self):
print("test_whatever_impossible is running")
self.assertEqual('n', 'n')
When I run the script above, it prints :
setup_test_data is running
setUpClass is running
test_whatever_impossible is running
.EtearDownClass is running
======================================================================
ERROR: test_whatever_possible_00001_error (main.TestWhatever)
Error!
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\ddt.py", line 145, in wrapper
return func(self, *args, **kwargs)
File "C:\ddt.py", line 187, in func
raise ValueError(message % file_attr)
ValueError: practice_test.json does not exist
----------------------------------------------------------------------
Ran 2 tests in 0.006s
FAILED (errors=1)
Then on the second run :
setup_test_data is running
setUpClass is running
test_whatever_impossible is running
.test_whatever_possible is running : testing
.tearDownClass is running
----------------------------------------------------------------------
Ran 2 tests in 0.005s
OK
I'm pretty much lost at this point...
Does anyone have a clue on this?
As a workaround you could write your own decorator to create the data file e.g.
def create_data(name):
def decorator(fn):
def decorated(*args, **kwargs):
data = pandas.DataFrame({"msg": ["testing"]})
data = data.transpose()
with open(name, "w") as file:
file.write(data.to_json())
res = fn(*args, **kwargs)
os.unlink(name)
return res
return decorated
return decorator
and then stack this for your test (and remove the setup_test_data step):
#ddt
class TestWhatever(unittest.TestCase):
#classmethod
def setUpClass(cls):
print("setUpClass is running")
#classmethod
def tearDownClass(cls):
print("tearDownClass is running")
#create_data("practice_test.json")
#file_data("practice_test.json")
def test_whatever_possible(self):
print("test_whatever_possible is running :")
self.assertEqual("q", "q")
Update: Jan 31st
The following example preserves the ddt behaviour of passing the parameter value into the test function:
import json
import unittest
from ddt import ddt, FILE_ATTR
def create_file_data(value, data):
def wrapper(func):
with open(value, "w") as file:
file.write(json.dumps(data))
# this is what the file_data decorator does
setattr(func, FILE_ATTR, value)
return func
return wrapper
#ddt
class TestWhatever(unittest.TestCase):
#create_file_data("practice_test.json", {"msg": ["testing"]})
def test_file_data(self, msg):
print("test_file_data msg:", msg)
unittest.main()
When this is run the output is:
test_file_data msg: ['testing']
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
Using the two examples above you should be able to derive an appropriate solution for your problem.
All Python code is executable. When a class body gets loaded, the statements in it are executed. A decorated function creates the function with the coffee inside the def, assigns it to the name (e.g. test_whatever_possible) in the temporary class namespace, and then replaces it with the result of calling the decorator.
The reason this is important is that the decorator is called when the class is loaded, not when the tests are run. The decorator file_data checks that the file exists right then and there (indirectly), since it has to replace your test function with one that calls your test with the values in the file.
Your second run passed because the first run had created the test file. I would recommend filling an issue on GitHub. Either file_data needs to delay opening the file, or the unexpected behavior needs to be documented somewhere clearly.
You can see exactly where this magic happens in the source code.file_data merely flags your test function for further processing. process_file_data then checks if the file can be used to expand the actual tests, or creates a replacement that just raises the error you see.
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)
Using Python 2.7, and mock library
How can I test that certain patched object has been initialized with some specific arguments using mock?
Here some sample code and pseudo-code:
unittest.py :
import mock
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
test1 = mock_someobject.return_value
test1 = method_inside_someobject.side_effect = ['something']
mylib.method_to_test()
# How can I assert that method_to_test instanced SomeObject with certain arguments?
# I further test things with that method_inside_someobject call, no problems there...
mylib.py :
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1=val1, arg2=val2, arg3=val3)
obj.method_inside_someobject()
So, how can I test SomeObject was instanced with arg1=val1, arg2=val2, arg3=val3?
If you replaced a class with a mock, creating an instance is just another call. Assert that the right parameters have been passed to that call, for example, with mock.assert_called_with():
mock_someobject.assert_called_with(arg1=val1, arg2=val2, arg3=val3)
To illustrate, I've updated your MCVE to a working example:
test.py:
import mock
import unittest
import mylib
class TestMyLib(unittest.TestCase):
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
mock_instance = mock_someobject.return_value
mock_instance.method_inside_someobject.side_effect = ['something']
retval = mylib.method_to_test()
mock_someobject.assert_called_with(arg1='foo', arg2='bar', arg3='baz')
self.assertEqual(retval, 'something')
if __name__ == '__main__':
unittest.main()
mylib.py:
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1='foo', arg2='bar', arg3='baz')
return obj.method_inside_someobject()
someobjectmodule.py:
class SomeObject(object):
def method_inside_someobject(self):
return 'The real thing'
and running the test:
$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK