Mocking ftplib.FTP for unit testing Python code

后端 未结 3 1089
不知归路
不知归路 2021-02-19 19:07

I don\'t know why I\'m just not getting this, but I want to use mock in Python to test that my functions are calling functions in ftplib.FTP correctly. I\'ve simplified everythi

相关标签:
3条回答
  • 2021-02-19 19:55

    I suggest using pytest and pytest-mock.

    from pytest_mock import mocker
    
    
    def test_download_file(mocker):
        ftp_constructor_mock = mocker.patch('ftplib.FTP')
        ftp_mock = ftp_constructor_mock.return_value
    
        download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
    
        ftp_constructor_mock.assert_called_with('ftp.server.local')
        assert ftp_mock.login.called
        ftp_mock.cwd.assert_called_with('pub/files')
    
    0 讨论(0)
  • 2021-02-19 19:57

    When you do patch(ftplib.FTP) you are patching FTP constructor. dowload_file() use it to build ftp object so your ftp object on which you call login() and cmd() will be mock_ftp.return_value instead of mock_ftp.

    Your test code should be follow:

    class TestDownloader(unittest.TestCase):
    
        @patch('ftplib.FTP', autospec=True)
        def test_download_file(self, mock_ftp_constructor):
            mock_ftp = mock_ftp_constructor.return_value
            download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
            mock_ftp_constructor.assert_called_with('ftp.server.local')
            self.assertTrue(mock_ftp.login.called)
            mock_ftp.cwd.assert_called_with('pub/files')
    

    I added all checks and autospec=True just because is a good practice

    0 讨论(0)
  • 2021-02-19 20:02

    Like Ibrohim's answer, I prefer pytest with mocker.

    I have went a bit further and have actually wrote a library which helps me to mock easily. Here is how to use it for your case.

    You start by having your code and a basic pytest function, with the addition of my helper library to generate mocks to modules and the matching asserts generation:

    import ftplib
    
    from mock_autogen.pytest_mocker import PytestMocker
    
    
    def download_file(hostname, file_path, file_name):
        ftp = ftplib.FTP(hostname)
        ftp.login()
        ftp.cwd(file_path)
    
    
    def test_download_file(mocker):
        import sys
        print(PytestMocker(mocked=sys.modules[__name__],
                           name=__name__).mock_modules().prepare_asserts_calls().generate())
        download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
    

    When you run the test for the first time, it would fail due to unknown DNS, but the print statement which wraps my library would give us this valuable input:

    ...
    mock_ftplib = mocker.MagicMock(name='ftplib')
    mocker.patch('test_29817963.ftplib', new=mock_ftplib)
    ...
    import mock_autogen
    ...
    print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
    

    I'm placing this in the test and would run it again:

    def test_download_file(mocker):
        mock_ftplib = mocker.MagicMock(name='ftplib')
        mocker.patch('test_29817963.ftplib', new=mock_ftplib)
    
        download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
    
        import mock_autogen
        print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
    

    This time the test succeeds and I only need to collect the result of the second print to get the proper asserts:

    def test_download_file(mocker):
        mock_ftplib = mocker.MagicMock(name='ftplib')
        mocker.patch(__name__ + '.ftplib', new=mock_ftplib)
    
        download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
    
        mock_ftplib.FTP.assert_called_once_with('ftp.server.local')
        mock_ftplib.FTP.return_value.login.assert_called_once_with()
        mock_ftplib.FTP.return_value.cwd.assert_called_once_with('pub/files')
    

    If you would like to keep using unittest while using my library, I'm accepting pull requests.

    0 讨论(0)
提交回复
热议问题