16

Trying to write a testcase for my class based function. This is skeleton of my class

class Library(object):
    def get_file(self):
        pass

    def query_fun(self):
        pass

    def get_response(self):
        self.get_file()

        response = self.query_fun()

        # some business logic here
        return response

I need to create a testcase that can mock just the query_fun and do the rest. tried below but seems is not the right direction:

from unittest import mock, TestCase
from library.library import Library

class TestLibrary(TestCase):
    def setUp(self):
        self.library = Library()

    @mock.patch('library.library.Library')
    def test_get_response(self, MockLibrary):
        mock_response = {
            'text': 'Hi',
            'entities': 'value',
        }
        MockLibrary.query_fun.return_value = mock_response
        response = MockLibrary.get_response()
        self.assertEqual(response, mock_response)

What I'm expecting is to setup mock and calling get_response will not actually call the original query_fun method, but instead call the mock query_fun.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343

3 Answers3

25

You need to take into account you mocked the whole class. You can mock individual methods too:

@mock.patch('library.library.Library.query_fun')
def test_get_response(self, mock_query_fun):
    mock_query_fun.return_value = {
        'text': 'Hi',
        'entities': 'value'
        }
    }
    response = MockBotter.get_response()
    self.assertIsNotNone(response)

Only Library.query_fun has been replaced, everything else in the class is still in place.

Simplified demo:

>>> from unittest import mock
>>> class Library(object):
...     def query_fun(self):
...         pass
...     def get_response(self):
...         return self.query_fun()
...
>>> with mock.patch('__main__.Library.query_fun') as mock_query_fun:
...     mock_query_fun.return_value = {'foo': 'bar'}
...     print(Library().get_response())
...
{'foo': 'bar'}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Mock just only the function what I want. I think this answer is what I'm looking for. But if I write another `test_function` iside the `TestLibrary` is not actually calling the `query_fun` mock method instead it's just call the normal method. –  Nov 21 '17 at 08:01
  • 1
    @RobC: each test needs have a `@mock.patch()` decorator. Mocks from one test don't carry over to another. – Martijn Pieters Nov 21 '17 at 08:02
  • 1
    Hm... I thought that too. One final question `Is there any way to replace self.botter.query_fun` with mock query_fun` ? –  Nov 21 '17 at 08:04
  • That's what `mock.patch()` is doing; mocking the original function on the class. `self.botter.query_fun` (or `self.library.query_fun` in your actual question example) is a *bound method*, which are created each time you access the name. If you are storing a reference to `self.library.query_fun` somewhere, it won't be replaced when mocking the method. – Martijn Pieters Nov 21 '17 at 08:06
3

I have used similar code below in my code, in case you want to mock the constructor too.

from unittest.mock import Mock

@mock.patch('library.library.Library')
def test_get_response(self, MockLibrary):
    mock_query_fun = Mock()
    mock_query_fun.return_value = {
        'text': 'Hi',
        'entities': 'value'
    }
    MockLibrary.return_value = Mock(
        query_fun=mock_query_fun
    )
    
    ...
Yuchen
  • 33
  • 4
0

An alternate pattern (based on answer from @Yuchen)

import mock
from unittest.mock import Mock

@mock.patch(
    'library.library.Library',
    return_value=Mock(query_fun=Mock(return_value={'text': 'Hi'}))
)
def test_get_response(self, MockLibrary):
    ...
    some_lib.some_func_that_uses_an_instance_of_Library()
    ...
Donn Lee
  • 2,962
  • 1
  • 24
  • 16