I have the following structure:
sources/
- parser/
- sources_parser.py # SourcesParser class is here
- tests/
- test_sources_parsers.py # SourcesParserTest is here
sources_parser.py
:
from sources.parser.sitemap_parser import SitemapParser
class SourcesParser(object):
__sitemap_parser: SitemapParser
def __init__(self) -> None:
super().__init__()
self.__sitemap_parser = SitemapParser()
def parse(self):
# ...
urls = self.__parse(source)
self.logger.info(f'Got {len(urls)} URLs')
def __parse(self, source: NewsSource) -> List[str]:
results = self.__sitemap_parser.parse_sitemap(source.url)
self.logger.info(f'Results: {results}, is list: {isinstance(results, list)}')
return results
Test:
class SourcesParserTest(TestCase):
@patch('sources.parser.sources_parser.SitemapParser')
def test_normal_parse(self, mock_sitemap_parser):
mock_sitemap_parser.parse_sitemap.return_value = [
'https://localhost/news/1',
'https://localhost/news/2',
'https://localhost/news/3',
]
parser = SourcesParser()
parser.parse()
And the logs are:
Results: <MagicMock name='SitemapParser().parse_sitemap()' id='5105954240'>, is list: False
Got 0 URLs
If I understand mocking correctly, the parse_sitemap
call should return the given list of URLs, but instead it returns a Mock
object which is converted to an empty list.
Python version is 3.9.
What's wrong?
CodePudding user response:
If mocking a member function, you have to mock the function on the mocked instance object, not the function on the mocked class object. This may not be completely intuitive, because a member function belongs to a class, but you may think about it as the bound versus unbound method - you have to mock the bound method.
With Mock
, mocking an instance is done by using return_value
on the class object, similar to mocking the result of a function call, so in your case you need:
@patch('sources.parser.sources_parser.SitemapParser')
def test_normal_parse(self, mock_sitemap_parser):
mocked_parser = mock_sitemap_parser.return_value # mocked instance
mock_parser.parse_sitemap.return_value = [
'https://localhost/news/1',
'https://localhost/news/2',
'https://localhost/news/3',
]
...
(split the mock for illustration only)