Saif Faidi
Saif Faidi

Reputation: 529

How to mock a function called in Class "A" and defined in class "B"?

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

Answers (1)

Kenny Ostrom
Kenny Ostrom

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

Related Questions