Home > Back-end >  Python mock returns invalid value
Python mock returns invalid value

Time:10-15

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)

  • Related