Search code examples
mockinggitlabintegration-testingpython-gitlab

python-gitlab mocking ProjectCommit objects API V4


I am trying to create a mock object of gitlab.v4.objects.ProjectCommit to perform integration tests between a tool I am developing with python-gitlab api. In order to create a ProjectCommit mock object by mocking the RESTManager object using ProjectCommit definition:

gitlab.v4.objects.ProjectCommit(
          manager: gitlab.base.RESTManager,
          attrs: Dict[str, Any], *, created_from_list: bool = False)
  • Therefore I tried the following
from unittest.mock import Mock
from gitlab.base import RESTManager, RESTObject
from gitlab.client import Gitlab

session_mock = Mock(spec=Gitlab)
rest_object_mock = Mock(spec=RESTObject)
                                          
rest_manager_mock = Mock(spec=RESTManager,                                         
                         gl=session_mock,                                          
                         parent=rest_object_mock)                                  

rest_manager_mock.gitlab = session_mock                                            
                                                  
# ProjectCommit mock object
# commit_recipe is just fake commit data
project_commit = ProjectCommit(manager=rest_manager_mock, attrs=commit_recipe)

  • Output
  File "gitlab_mocks.py", line 24, in <module>
    ProjectCommit(manager=rest_manager_mock,
  File "env_auto/lib/python3.8/site-packages/gitlab/base.py", line 88, in __init__
    self._create_managers()
  File "env_auto/lib/python3.8/site-packages/gitlab/base.py", line 207, in _create_managers
    manager = cls(self.manager.gitlab, parent=self)
  File "env_auto/lib/python3.8/site-packages/gitlab/base.py", line 356, in __init__
    self._computed_path = self._compute_path()
  File "env_auto/lib/python3.8/site-packages/gitlab/base.py", line 371, in _compute_path
    data = {
  File "env_auto/lib/python3.8/site-packages/gitlab/base.py", line 372, in <dictcomp>
    self_attr: getattr(self._parent, parent_attr, None)
  File "env_auto/lib/python3.8/site-packages/gitlab/base.py", line 126, in __getattr__
    return self.__dict__["_parent_attrs"][name]
TypeError: 'Mock' object is not subscriptable

Looking at RestObject implementation, ProjectCommit initialisation is failing here,

How can I overcome this problem ? Is there an alternative way to create ProjectCommit mock object?

You can find ProjectCommit documentation here


Solution

  • The real thing you need to mock is the outside system (e.g. gitlab returning a particular response). It's easier to mock the HTTP response instead of the library internals. This probably also makes more sense than mocking out core components of the library -- otherwise you'd just be testing the "integration" between your code and mocks, which is somewhat useless.

    One way would be to selectively patch the http_ methods of your Gitlab (gl) object when you want to mock the server response.

    For example, to mock out the response when calling project.commits.get:

    from unittest import mock
    # ...
    # false commit response data
    # https://docs.gitlab.com/ee/api/commits.html#get-a-single-commit
    fake_data = {'id': 'abc123',
     'short_id': 'abc',
     'created_at': '2022-04-01T02:23:14.000+00:00',
     'parent_ids': ['a', 'b'],
     'title': "A fake title",
     'message': 'A fake message',
     'author_name': 'Fake Author',
     'author_email': '[email protected]',
     'authored_date': '2022-04-01T02:23:14.000+00:00',
     'committer_name': 'Fake Committer',
     'committer_email': '[email protected]',
     'committed_date': '2022-04-01T02:23:14.000+00:00',
     'trailers': {},
     'web_url': 'https://gitlab.example.com/fake/project/-/commit/abc123',
     'project_id': 1234}
    
    def fake_response(*args, **kwargs):
        """Used to replace Gitlab.http_get (or other) method(s)"""
        return fake_data
    
    with mock.patch.object(gl, 'http_get', new=fake_response):
        fake_commit = p.commits.get(id='abc123')
    

    The result of fake_commit will be a ProjectCommit object as if GitLab responded with the fake api data.

    >>> fake_commit
    <ProjectCommit id:abc123>
    

    You could make the patched function more elaborate to return data based on the URL provided, for example.