pythonmockingconnectionpytest

pytest - mock config[<section_name>] via configparser.ConfigParser() / config.read()


aim: I want to test our web service Connection Wrapper for e.g. GitLab, SFTP, MySQL.

I am stuck in the following part:

config = configparser.ConfigParser()
config.read(r"C:\git\config.ini")

How do I mock/patch/MagicMock so that config[<section_name>] is returning a in test_gitlab.py defined value for the given key. As we got various calls of the above structure in various .py files , I'd like, if somehow possible, not change the actual config.read(r"C:\git\config.ini") call.

Approaches that seemed promising but failed:

    # ./module/io/gitlab.py
    import configparser
    import os
    import sys
    import gitlab
    
    class GitlabApi:
        def __init__(self, client=None):
            if sys.platform == "win32":
                config = configparser.ConfigParser()
                config.read(r"C:\git\config.ini")
                self.token = config["GITLAB_{}".format(client)]["token"]
    
        def return_client(self):
            self.api = gitlab.Gitlab(
                "https://gitlab.company_name.com", 
                private_token=self.token
            )
            self.api.auth()
            return self.api
    
    
    # ./tests/test_gitlab.py
    import pytest
    from unittest.mock import MagicMock
    from unittest.mock import Mock
    from ane.io import gitlab
    
    def test_return_client_win32():
        gitlab.sys = Mock(platform="win32")
        gitlab.gitlab.Gitlab = MagicMock()
    
        test_client, expected_private_token, expected_url = "test", "test_token", "test_url"
        config_dict = {
            "GITLAB_TEST": {"token": "test_token"},
            "GITLAB_SCRIPTS": {"token": "script_token"},
        }
    
    
        # following does not work
        gitlab.configparser.ConfigParser = MagicMock()
        gitlab.configparser.ConfigParser.return_value.read.return_value = config_dict
    
    
        gitlab.GitlabApi(client=test_client)  # mocked object
        gitlab.gitlab.Gitlab.assert_called_once_with(
            expected_url, private_token=expected_private_token
        )

A few stackoverflows that did not solve it for me, but might help others:

python 3.7


Solution

  • My solution to the above stated question: Mocking built-in open() which config.read() calls. The returned fake_config_file is basically a file in bytes.

    def test_return_client_win32():
        gitlab.sys = Mock(platform="win32")
        gitlab.gitlab.Gitlab = MagicMock()
    
        test_client = 'client'
        expected_private_token = 'expected_private_token'
        expected_url = "https://gitlab.company.name"
        fake_config_file = TextIOWrapper(
            BytesIO(
                b"[GITLAB_TEST]\ntoken: test_token\n"
                b"[GITLAB_SCRIPTS]\ntoken: script_token"
            )
        )
    
        gitlab.configparser.open = MagicMock(return_value=fake_config_file)
        gitlab.GitlabApi(client=test_client)
        gitlab.gitlab.Gitlab.assert_called_once_with(
            expected_url, private_token=expected_private_token
        )
    

    In General, my advised approach at the moment is: Look for a string-type return. Go deep into the methods until you find a method that returns a string or similar. This is what you mock.