mercredi 16 novembre 2016

Mocked method claims it has more calls than expected

I have a class, QueryBuilder, which sends the message build to an instance of another class, Query. The method that sends that message is called build_query. For the purpose of this question, I'm only concerned with what values QueryBuilder sends to Query's build method.

My test for QueryBuilder asserts that Query's build method is called once for each set of "grouped params" sorted by QueryBuilder.

def test_groups_queries_based_on_config(self):
    params = {'metatag_attr': 'name', 'metatag_val': 'description', 'robots': True}
    with patch.object(Query, 'build') as mock_query_build:
        self.query_builder.build_queries(params)
        calls = [call({'metatag_attr': 'name', 'metatag_val': 'description'}),
                 call({'robots': True})]
        mock_query_build.assert_has_calls(calls)

Here is the code for QueryBuilder.

class QueryBuilder(object):
    def __init__(self, config=None):
        self._queries = []
        self._collected_params = None
        self._config = config or {}

    ...

    def build_queries(self, params):
        # should return an iterator of built queries
        self._collected_params = params
        for param_set in self._group_params():
            query = self.build_query(param_set)
            self._append_query(query)
        return self._queries

    def build_query(self, param_set):
        query = self._new_query()
        return query.build(param_set)

    ### Private ###
    def _new_query(self):
        return Query(shortcuts=self.shortcuts, option_groups=self.options)

    def _append_query(self, query):
        valid_query = {k: v for k, v in query.items() if not k == 'seo_attr'}
        if not valid_query:
            return
        self._queries.append(query)

    def _group_params(self):
        # initialize list to collect all params
        grouped_params = []
        # add the option params to the list
        options = self._get_options()
        grouped_params.extend(options)
        # add the shortcut params to the list
        shortcuts = self._get_shortcuts()
        grouped_params.extend(shortcuts)
        return grouped_params

    ...

Some private methods and public properties have been left out because I don't believe they pertain to the question.

The issue is that after I added this line to QueryBuilder's private _append_query method...

valid_query = {k: v for k, v in query.items() if not k == 'seo_attr'}
if not valid_query:
    return

...I'm now getting several extra calls to the build method, causing my test to fail. These are the extra calls to build:

E       Expected: [call({'metatag_val': 'description', 'metatag_attr': 'name'}), call({'robots': True})]
E       Actual: [call({'metatag_val': 'description', 'metatag_attr': 'name'}),
E       call().items(),
E       call().items().__iter__(),
E       call({'robots': True}),
E       call().items(),
E       call().items().__iter__()]

I don't see how sending items to query in the dict comprehension is treated as a call to build again. What would explain this?

Aucun commentaire:

Enregistrer un commentaire