Monkey patching a class in another module in Python

前端 未结 6 1078
走了就别回头了
走了就别回头了 2020-11-27 12:07

I\'m working with a module written by someone else. I\'d like to monkey patch the __init__ method of a class defined in the module. The examples I have found sh

6条回答
  •  孤独总比滥情好
    2020-11-27 12:35

    Here is an example I came up with to monkeypatch Popen using pytest.

    import the module:

    # must be at module level in order to affect the test function context
    from some_module import helpers
    

    A MockBytes object:

    class MockBytes(object):
    
        all_read = []
        all_write = []
        all_close = []
    
        def read(self, *args, **kwargs):
            # print('read', args, kwargs, dir(self))
            self.all_read.append((self, args, kwargs))
    
        def write(self, *args, **kwargs):
            # print('wrote', args, kwargs)
            self.all_write.append((self, args, kwargs))
    
        def close(self, *args, **kwargs):
            # print('closed', self, args, kwargs)
            self.all_close.append((self, args, kwargs))
    
        def get_all_mock_bytes(self):
            return self.all_read, self.all_write, self.all_close
    

    A MockPopen factory to collect the mock popens:

    def mock_popen_factory():
        all_popens = []
    
        class MockPopen(object):
    
            def __init__(self, args, stdout=None, stdin=None, stderr=None):
                all_popens.append(self)
                self.args = args
                self.byte_collection = MockBytes()
                self.stdin = self.byte_collection
                self.stdout = self.byte_collection
                self.stderr = self.byte_collection
                pass
    
        return MockPopen, all_popens
    

    And an example test:

    def test_copy_file_to_docker():
        MockPopen, all_opens = mock_popen_factory()
        helpers.Popen = MockPopen # replace builtin Popen with the MockPopen
        result = copy_file_to_docker('asdf', 'asdf')
        collected_popen = all_popens.pop()
        mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
        assert mock_read
        assert result.args == ['docker', 'cp', 'asdf', 'some_container:asdf']
    

    This is the same example, but using pytest.fixture it overrides the builtin Popen class import within helpers:

    @pytest.fixture
    def all_popens(monkeypatch): # monkeypatch is magically injected
    
        all_popens = []
    
        class MockPopen(object):
            def __init__(self, args, stdout=None, stdin=None, stderr=None):
                all_popens.append(self)
                self.args = args
                self.byte_collection = MockBytes()
                self.stdin = self.byte_collection
                self.stdout = self.byte_collection
                self.stderr = self.byte_collection
                pass
        monkeypatch.setattr(helpers, 'Popen', MockPopen)
    
        return all_popens
    
    
    def test_copy_file_to_docker(all_popens):    
        result = copy_file_to_docker('asdf', 'asdf')
        collected_popen = all_popens.pop()
        mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
        assert mock_read
        assert result.args == ['docker', 'cp', 'asdf', 'fastload_cont:asdf']
    

提交回复
热议问题