I would like to test by mocking an instance's attribute, but I do not have access to the instance beforehand. How can I mock the attribute without it? Here is my minimum reproducible code.
# test.py
class Foo:
def __init__(self, x):
self.x = x
def bar():
return Foo(1).x + 1
def test_bar(mocker):
mocker.patch('test.Foo.x', 2)
assert bar() == 3
$ pytest test.py
FAILED test.py::test_bar - AttributeError: <class 'test.Foo'> does not have the attribute 'x'
This makes sense since the Foo class doesn't have an x, only instances. If I add a kwarg mocker.patch('test.Foo.x', 2, create=True) I then get this
$ pytest test.py
FAILED test.py::test_bar - assert 2 == 3
since Foo.x will get mocked but overridden when the instance later sets self.x = x.
The standard way to do this kind of test is to mock the entire class, like so:
def test_bar(mocker):
mock_foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_foo)
mock_foo.return_value.x = 2
assert bar() == 3
mock_foo.assert_called_once_with(1)
So in this case, you mock the entire Foo class.
Then, there is this syntax mock_foo.return_value.x = 2: where mock_foo.return_value simply means the return object when calling Foo(1) - which is your object. Since we mocked the entire class - the __init__ method does nothing - so you need to set the x attribute on your own.
Also note the mock_foo.assert_called_once_with(1) - which checks that Foo(1) was called with the right parameter.
p.s.
To simplify this kind of mocking, I created a pytest fixture called pytest-mock-generator. You can use it to help you create the mocks and asserts.
You start with using the mg fixture to analyze your bar method so it locates the relevant mocks for you:
def test_bar(mocker, mg):
mg.generate_uut_mocks(bar)
When this test method is executed, it generates the following code (prints it to the console and copies it to your clipboard for quick usage):
# mocked dependencies
mock_Foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_Foo)
You then modify the test function and execute the bar method and use the generate_asserts capability:
def test_bar(mocker, mg):
# mocked dependencies
mock_Foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_Foo)
bar()
mg.generate_asserts(mock_Foo)
This gives you the following output:
assert 1 == mock_Foo.call_count
mock_Foo.assert_called_once_with(1)
mock_Foo.return_value.x.__add__.assert_called_once_with(1)
You don't need most of it, but you can keep the second line for your assert and the modify the third line so x has the right value:
def test_bar(mocker):
mock_foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_foo)
mock_foo.return_value.x = 2
assert bar() == 3
mock_foo.assert_called_once_with(1)
Related
I have a question regarding parametrizing the test method with another method that returns the list of test data that I want to use in my test:
When I execute code in this way:
class Test:
#pytest.mark.parametrize("country_code", get_country_code_list())
def test_display_country_code(self, country_code):
print(country_code)
#classmethod
def get_country_code_list(cls) -> list:
return [1, 2, 3]
I get error: Unresolved referency: get_country_code_list. It doesn't make a difference if get_country_code_list method is a static method, class method or self.
But if I put the method get_country_code_list() above the test method, I don't get this error.
Does the order of test methods make a difference in Python?
Yes, the order in which you do things is important.
Functions are just like variables in that manner.
Working Example Variables
x = 3
y = 5
z = x + y
Works perfectly fine, because everything is done according to order.
Broken Example Variables
x = 3
z = x + y
y = 5
Doesn't work, of course, because y is neither declared nor defined when y is needed for the initialization of z.
Works just the same with functions
def bar():
return foobar()
def foo():
return bar()
foo()
def foobar()
return 5
Function foo can call function bar perfectly fine, but bar can't call foobar, because foobar is not defined yet at the execution point of foo().
This isn't a test-specific issue only.
You need to understand that #pytest.mark.parametrize is a decorator, a syntactic sugar of a class / method. When you pass an argument in a class / method, it expects the argument to be defined. Hence why this works:
def display(arg):
return print(arg)
word = "Hello World!"
display(word)
while this does not:
def display(arg):
return print(arg)
display(word)
word = "Hello World!"
Here's an example of a class-based decorator:
class MyDecorator:
def __init__(self, decor_arg):
self.decor_arg = decor_arg
def __call__(self, fn):
def wrapper(fn_arg):
return fn_arg
return self.decor_arg
def message(arg):
return f"The parameter passed is {arg}"
#MyDecorator(message)
def display(arg):
return arg
print(display("Hello World!"))
The print result:
The parameter passed is Hello World!
Given the explanation above, I'm sure you can see why the method message needs to be placed before display. If your editor has IntelliSense, changing the order of the two methods will display an error outline on #MyDecorator(concat) with the message "undefined name 'message'" or something similar.
I am having trouble with checking if an object is being constructed with the proper params from another instance of an object. In the below example, I am trying to create an instance of B within an instance of A. I want to check the parameter being used in the constructor of B inside of the A instance. When I run the test below, I get:
AssertionError: assert None
[CPython36:setup:stdout] E + where None = <bound method NonCallableMock.assert_called_with of <MagicMock name='B' id='139968329210736'>>(4)
[CPython36:setup:stdout] E + where <bound method NonCallableMock.assert_called_with of <MagicMock name='B' id='139968329210736'>> = <MagicMock name='B' id='139968329210736'>.assert_called_with
I am not quite sure what I am doing wrong here and have looked at other stack overflow posts, but have not been able to solve my issue.
b.py:
class B(object):
def __init__(self, x):
self.x = x
def square(self):
return x * x
a.py:
from b import B
class A(object):
def foo(self):
b = B(4)
b.square()
test_a.py:
import unittest
from unittest.mock import patch
from a import A
class TestA(unittest.TestCase):
#patch('a.B')
def test_foo(self, mock_b):
self.a = A()
self.a.foo()
assert mock_b.assert_called_with(4)
The method assert_called_with returns None, so what your are doing is like doing
assert None
And that's basically the error message you are getting.
You can just use
mock_b.assert_called_with(4)
Which has an assert internally and pytest will display it correctly in case of failure. Try to check it by changing the argument value.
Alternatively, if you prefer to write the assert yourself, you can do something like this:
from unittest.mock import call
assert mock_b.call_args_list == [call(4)]
Or just the last call:
from unittest.mock import call
assert mock_b.call_args == call(4)
I observe a strange behavior with Python 2.7.12 and Python 3.5.2:
import sys
class Foo:
def __init__(self):
self.b = self.bar
def bar(self):
pass
f = Foo()
print(sys.getrefcount(f) - 1) # subtract the extra reference created by
# passing a reference to sys.getrefcount.
When I run the code in python I get 2 which means that there are two references to the Foo object. The only solution is to remove self.b = self.bar. It seems to create a cycle reference.
Can anyone explain the behavior?
UPDATE:
Here are the run results:
class Foo:
def __init__(self):
self.b = self.bar
def bar(self):
pass
def __del__(self):
print "deleted"
def test_function():
f = Foo()
print f
if __name__ == "__main__":
for i in xrange(5):
test_function()
print "finished"
python ./test.py
<__main__.Foo instance at 0x104797e60>
<__main__.Foo instance at 0x104797ef0>
<__main__.Foo instance at 0x104797f38>
<__main__.Foo instance at 0x104797f80>
<__main__.Foo instance at 0x104797fc8>
finished
As you may see on every iteration python creates a new instance of the class Foo, but never releases them!
UPDATE 2 AND THE ROOT CAUSE
Objects that have del() methods and are part of a reference cycle
cause the entire reference cycle to be uncollectable, including
objects not necessarily in the cycle but reachable only from it.
https://docs.python.org/2/library/gc.html#gc.garbage
Foo class instance has both a __del__ method and the bound method self.b = self.bar which means it is definitely a part of the reference cycle.
Thus, according to py docs the instance is uncollectable!
The extra reference is hidden in the bound method stored in f.b.
print(f.b.__self__) # <__main__.Foo object at 0x000001CD147FEF98>
When storing self.b = self.bar, you create a bound method. A bound method keeps track of the instance to which it is bound. This is what allows it to pass self implicitly.
The same would happen if you were to manually create the bound method.
import sys
class Foo:
def bar(self):
pass
f = Foo()
print(sys.getrefcount(f) - 1) # 1
bound_method = f.bar
print(sys.getrefcount(f) - 1) # 2
So you are correct: instantiating a bound method creates the reference cycle f -> f.b -> f. Although, this is not an issue since Python handles cyclic reference since version 2.0.
How to mock a method that is not in the scope of a test?
or: How to mock a method that is not called directly?
In this case method baz
I'm using the Mock package from pypi
### tests
# ...
def test_method_a(self):
# how to mock method that is called from bar() ?
obj = foo.bar()
self.assertEqual(obj.get('x'), 12345)
### foo
# ...
def bar():
x = some_module.baz() # <- how to mock baz() ?
return x
Here is an example that should show you how it works:
from mock import patch
def baz():
return 'y'
def bar():
x = baz() # <- how to mock baz() ?
return x
def test():
with patch('__main__.baz') as baz_mock:
baz_mock.return_value = 'blah'
assert bar() == 'blah'
test()
IMHO this example better illustrates the common use case during unit testing.
The important part is the #patch decorator which effectively substitutes the side_effect function (this could also just be a value) for the function being patched. The tricky bit is often getting the full package path to the patched function. Notice how you must use '__main__.func' to reference the patched function.
The first test, test_func checks that the mocked value of the test is the original expected function value ('Hello World!'). The second test, test_mocked_func patches func to actually be the function object new_func, hence returning True. The third test illustrates substituting in values rather than a new function as the side_effect. In fact, since we made the substitution value an iterable (side_effect=['New String 1!', 'New String 2!', 3]), each time it runs, it will return a new value.
Warning: If you try to call your patched function more times than you have specified return values (3 in this case), you will get a StopIteration error since you didn't define enough return values in side_effect.
import unittest
from mock import patch # for Python 2.7
# from unittest.mock import patch # for Python 3.5
def func():
return 'Hello World!'
def newFunc():
return True
class TestFunc(unittest.TestCase):
def test_func(self):
self.assertEqual(func(), 'Hello World!')
#patch('__main__.func', side_effect=newFunc)
def test_mocked_func(self, *patches):
self.assertTrue(func())
#patch('__main__.func', side_effect=['New String 1!', 'New String 2!', 3])
def test_mocked_func_again(self, *patches):
self.assertEqual(func(), 'New String 1!')
self.assertEqual(func(), 'New String 2!')
self.assertEqual(func(), 3)
# func() # This breaks the test because we only specified a list of length 3 in our patch.
if __name__=='__main__':
unittest.main()
I try to use nose test
But when i run the testcase below
import unittest
class TestSuite(unittest.TestCase):
b = []
def setUp(self):
self.b.extend([10, 20])
def tearDown(self):
self.b = []
def test_case_1(self):
self.b.append(30)
assert len(self.b) == 3
assert self.b == [10, 20, 30]
def test_case_2(self):
self.b.append(40)
assert len(self.b) == 3
assert self.b == [10, 20, 40]
But all test cases is not pass
$> nosetest test_module.py
.F
======================================================================
FAIL: test_case_2 (test_module2.TestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/knt/test_module2.py", line 19, in test_case_2
assert len(self.b) == 3
AssertionError
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
What 's happend ??? I expect after run test_case_1, tearDown will be called, so self.b is []. So with next test case test_case_2, setUp run and self.b is [10, 20].
But in fact, at setUp value of self.b is [10, 20, 30].
I dont know why. I think there must be some problems with statement self.b = [].
Anything related pointers, I guess?
I still didn't figure it out, but i find way to fix this bug. Just change self.b = [] to del self.b[:].
Anyone can help me find out the problem?
Thank you so much.
As far as I can tell the problem maybe with the way unitests works and how class fields work in python here is a simple test:
class A:
b = []
def reset(self):
self.b = []
a = A()
a.b.append(3) # we are actually accessing the class variable here
print A.b is a.b # True
print a.b # [3] same here
a.reset() # We just hid the class variable with our own which points to []
print A.b is a.b # False as expected.
print a.b # [] we think its being clear but rather we are using our own not the class variable
b = A()
print b.b # [3] b here is using the class that a previously modified but is no longer pointing to
print b.b is A.b # True
# Also note
c = A()
d = A()
print c.b is d.b # True, since both are using the same class variable.
I think unittest is creating the object multiple times, for each test function, creates object, fires setup which access the class variable, the test runs, calls teardown which simply hides it, creates another object, calls setup which access the same class variable that the previous object modified and didn't affect since its tear down simply created a new instance binded to self, hiding the class version.
we always declare member fields inside the __init__ using self.
def __init__(self):
self.b = []
this way every instance will have its own copy, though we can't do this here since we are inheriting from unittest.TestCase thats why we have setUp
import unittest
class TestSuite(unittest.TestCase):
def setUp(self):
self.b = [10, 20]
def tearDown(self):
self.b = []
def test_case_1(self):
self.b.append(30)
assert len(self.b) == 3
assert self.b == [10, 20, 30]
def test_case_2(self):
self.b.append(40)
assert len(self.b) == 3
assert self.b == [10, 20, 40]
The problem is your class attribute in line 2:
b = []
That is an attribute on the class TestSuite. Nosetests creates a new instance of the class TestSuite, and then calls setUp. In setUp, you modify the class attribute on the class TestSuite:
self.b.extend([10, 20])
Then, in tearDown, after your first test has been run, you create a new list and assign it to a new instance attribute which is also happens to be called b:
self.b = []
This does not modify the class attribute at all. Further attempts to access self.b from this instance will return the instance attribute, not the class attribute.
But this has no effect, because the very next thing that happens is that nosetests throws away the current instance of TestSuite, including your new, empty list in the instance attribute b that you have set in tearDown.
Then nosetests creates a brand new instance of the class TestSuite, to run your second test. But the class TestSuite still has a b class attribute containing [10, 20, 30], because it was modified while your first test was running.
Then nosetests setUp method runs, adds 10 and 20 to TestSuite's class attribute b. Then your second test runs, adds 40 to TestSuite's class attribute, and your second test fails, because it finds six items in that class attribute:
[10,20,30,10,20,40]
The reason that del self.b[:] works is because, just like append, del is modifying the class attribute, rather than creating a new instance attribute.
Make sure you understand the difference between classes and instances, and between class attributes and instance attributes, otherwise you will be plagued with similar problems for as long as you are working with Python.
Well, try to replace the:
self.b.extend([10,20])
with
self.b = [10,20]
and maybe throw out the class variable, you don't need it and it might be the reason this happens.