Reputation: 529
I'm writing a unit test code to test a function "a" defined in Class A that calls a function "b" defined in Class "B". How can I make "a" calls the b-mocking function?
#-------------------------------------------------
class B :
def __init__(self):
print('hello B')
def b(self):
return False
#------------------------------------------------
class A :
def __init__(self):
print('hello A')
def a(self,a,b):
ret = B().b()
if ret == True:
return 0
else :
return a+b
#-----------------------------------------------
class TestA(unittest.TestCase):
def setUp(self):
pass
def return_true(self):
return True
@patch('__main__.B')
def test_a_in_A(self,mocked_B):
mocked_B.b.return_value = True
assert A().a(2,3) == 0
#---------------------------------------------
if __name__ == '__main__':
unittest.main()
i expected output of assert A().a(2,3) == 0 to be test OK
but the actual output is AssertionError
Note: when I ran assert A().a(2,3) == 5, the test returns test OK
which means that the mock is not working correctly.
Upvotes: 0
Views: 716
Reputation: 5871
Okay, long version: Mocking is when you replace something with a simplified form. However, you can't mock something that was not used. It doesn't make any sense.
Let's look at your code here:
def a(self,a,b):
ret = B().b()
if ret == True:
return 0
else :
return a+b
You are constructing a new B() inside this function, so it doesn't have the return_value you set elsewhere in the test on mocked_B. The whole point of having a mock object is that you pass the mock in, and you get its predictable behavior. You can't mock an object that is entirely encapsulated inside the function being tested.
Here I have modified your code to take a (possibly mocked) B object. I renamed the integer arguments from a, b to x, y because it was too confusing. They have nothing to do with A or B.
Also you forgot some imports.
import unittest
from unittest.mock import patch
class B :
def __init__(self):
print('hello B')
def b(self):
return False
class A :
def __init__(self):
print('hello A')
def a(self, x, y, b):
if b.b():
return 0
else:
return x + y
class TestA(unittest.TestCase):
def setUp(self):
pass
def return_true(self):
return True
@patch('__main__.B')
def test_a_in_A(self,mocked_B):
mocked_B.b.return_value = True
assert A().a(2,3, mocked_B) == 0
if __name__ == '__main__':
unittest.main()
As requested, here is a revised version with less impact on existing code:
class A :
def __init__(self, b=None):
# this instance can use a member instance of B
self.b = b if b else B()
print('hello A')
def a(self, x, y):
if self.b.b() is True:
return 0
else:
return x + y
I changed the test to "self.b.b() is True" to avoid running into a MagicMock object with no return_value initialized nevertheless evaluating to true because ... it is an instance, and that's how bool(object) works.
Here you still have to pass in the mock instance which has the return_value set to what you want, so the test would be
assert A(mocked_B).a(2,3) == 0
You have to have something to replace, if you want the mock object to replace something. It might help to look up "dependency injection" which is another name for the same basic idea.
Upvotes: 1