amrx
amrx

Reputation: 713

Test whether a function is called inside another function in unit testing

New to mock and patch in python. I have a class which has a method update

class myclass(object):
   def update(self, name, passwd):
       self.update_in(name,passwd)
   def update_in(self, name, passwd):
       self.name = name
       self.passwd = passwd

Now in another class I have to test the update method and ascertain that the update method calls in update_in method. How do I achieve this ?

Upvotes: 3

Views: 6433

Answers (2)

Michele d'Amico
Michele d'Amico

Reputation: 23711

By patch from unittest.mock module you can patch methods, functions, object or attribute of your production code. Now I assume myclass in mymodule and I will show you some simple way of how to do your test.

from unittest.mock import patch
from mymodule import myclass

m = myclass()
with patch("mymodule.myclass.update_in", autospec=True) as mock_update_in:
    m.update('me', 'mypassword')
    mock_update_in.assert_called_with('me', 'mypassword')

@patch("mymodule.myclass.update_in", autospec=True)
def my_test(mock_update_in):
    m = myclass()
    m.update('me', 'mypassword')
    mock_update_in.assert_called_with('me', 'mypassword')

my_test()

Now instead of patch you can use patch.object(myclass, "update_in", autospec=True) and patch the reference of myclass in the module of your tests. My feel is to use patch.object just when you cannot do otherwise: you must be sure that you are patching the code that will be called by your test and not something else. For instance you have mymodule_b the use from mymodule import myclass and now you test a method in mymodule_b like:

from mymodule import myclass
def get_registered(username, password):
    m = myclass()
    m.update(username, password)
    return m

Now the reference of myclass used by get_registered() is not the one in your test module. Next test will fail

from mymodule import myclass
from mymodule_b import get_registered

with patch.object(myclass, "update_in", autospec=True) as mock_update_in:
    m = get_registered('me', 'mypassword')
    assert m is not None
    mock_update_in.assert_called_with('me', 'mypassword')

Is a good practice take a look to Where to patch session before to start to ride patch functions.

Just a note about use autospec=True: autospec is a real powerful option of patch functions family, your patched object will take the signature and the attributes from the original reference and prevent some silly errors in your test. To understand the value of autospec take a look to the next example:

m = myclass()
with patch("mymodule.myclass.update_in") as mock_update_in:
    m.update('me', 'mypassword')
    mock_update_in.assert_call_with('you', 'yourpassword')

The previous test pass even if you check by the wrong arguments just because mock_update_in is a standard MagicMock() return a MagicMock object for every attribute you ask or every method you call without raise any exception: in that scenario mock_update_in.assert_call_with('you', 'yourpassword') will return a MagicMock().

Upvotes: 3

Daniel Roseman
Daniel Roseman

Reputation: 599490

You should use mock.patch to replace the method with a mock, and then you can assert various things about the mock after calling your update method.

patcher = mock.patch.object(myclass, 'update_in')
patched = patcher.start()

m=myclass()
m.update('foo', 'bar')

assert patched.call_count == 1
patched.assert_called_with('foo', 'bar')

Upvotes: 1

Related Questions