I need to mock docker server for unit tests in python

Viewed 1060

I have developped a class that uses the docker library with

self.client = docker.from_env(timeout=5, version="auto")

and as example:

def run_container_in_background(self,application,branch,name_container,mount_dir):
    container =self.client.containers.run(image,command"bash",detach=True,name=name_container)

I need to make a unit test for this method so i need to mock the docker server. So does anyoe have an idea how to mock it using python?

When I try to mock "self.client = docker.from_env(timeout=5, version="auto")"

self = <docker.api.client.APIClient object at 0x0EE644D0>

def _retrieve_server_version(self):
    try:
        return self.version(api_version=False)["ApiVersion"]
    except KeyError:
        raise DockerException(
            'Invalid response from docker daemon: key "ApiVersion"'
            ' is missing.'
        )
    except Exception as e:
        raise DockerException('Error while fetching server API version: 
{0}'.format(e))
E           docker.errors.DockerException: Error while fetching server API version: (2, 'CreateFile', 'Le fichier spécifié est introuvable.')

PS: the docker daemon is deactivate to reproduce the same issue i want to solve. Is there any way to solve the mocking problem to fix this issue?

1 Answers

You should probably refactor this class to take the Docker client as a parameter, rather than create its own. In addition to supporting this testing setup, this will also help you do things like share connection pools between objects and avoid repeating some details like that timeout.

class ObjectUnderTest:
  def __init__(self, client):
    self.client = client

  def run_container_in_background(self, application, branch, name_container, mount_dir):
    self.client...

def main():
  client = docker.from_env(timeout=5)
  obj = ObjectUnderTest(client)

Having done this, you can create any object you want and pass it in as the client parameter. Especially since Python is generally dynamically typed, you can use your mock object in place of the real client, just so long as the object you create has the same "shape" as the calling code expects.

class MockContainer:
  '''A fake Docker API container object.'''
  def __init__(self, *args, **kwargs):
    self.args = args
    self.kwargs = kwargs

class MockContainersApi:
  '''A fake Docker API with containers calls.'''
  def run(self, *args, **kwargs):
    return MockContainer(*args, **kwargs)

class MockDockerApi:
  '''A fake Docker API.'''
  def __init__(self):
    self.containers = MockContainersApi()

def test_run_container():
  client = MockDockerApi()
  obj = ObjectUnderTest(client)
  container = obj.run_container_in_background(...)
  assert container.kwargs['detach'] == True

(Also consider how much your application needs to call the Docker API directly, and if it could be refactored to use other technology. As you note in this question, the Docker API is complex and the required testing approach isn't simple. Since you can bind-mount an arbitrary host directory into a container, it's also very easy to get a container running as root that can write to the host's /etc directory, and thereby root the whole host.)

Related