Friday, February 4, 2011

Mocking libcloud using Mox

If you're using libcloud to interact with cloud providers, you probably would like to cover this code with tests if you haven't done so already.

However, most likely you don't want libcloud to do any real calls to the cloud provider API service as it could create additional noise on your account, add extra load and cost you money (esp. if tests are being run on a regular basis on a Continues Integration server).

One possible solution is to mock these things up using Mox mocking framework. Libcloud uses 2-step initialisation process for a connection class -- first you obtain driver class and then initiate it -- and I seem to always forgot how to mock it right, so decided to write it down.

For example, we have a simple class that returns a list of names of currently available nodes:

#!/usr/bin/env python
import libcloud.providers
import libcloud.types
from secret import USERNAME, PASSWORD
class Example(object):
def __init__(self):
driver = libcloud.providers.get_driver(
libcloud.types.Provider.RACKSPACE
)
self.connection = driver(USERNAME, PASSWORD)
def node_names(self):
return [node.name for node in self.connection.list_nodes()]
if __name__ == "__main__":
example = Example()
print example.node_names()
view raw example.py hosted with ❤ by GitHub


And we want to test it without making actual API calls. Let's see how the test code will look for this task. For the sake of simplicity default unittest framework will be used.

#!/usr/bin/env python
import unittest
import mox
import libcloud.providers
from libcloud.drivers.rackspace import RackspaceNodeDriver
from example import Example
class TestExample(unittest.TestCase):
def setUp(self):
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
def test_that_node_names_calls_list_nodes(self):
mocked_driver = self.mox.CreateMock(RackspaceNodeDriver)
mocked_driver.list_nodes().AndReturn([
type("Node", (), {"name": "node1"}),
type("Node", (), {"name": "node2"})
])
self.mox.StubOutWithMock(libcloud, "providers",
use_mock_anything=True)
libcloud.providers.get_driver(mox.IgnoreArg()).AndReturn(
lambda *args, **kwargs: mocked_driver)
self.mox.ReplayAll()
example = Example()
node_names = example.node_names()
self.assertEqual(set(node_names), set(["node1", "node2"]))
self.mox.VerifyAll()
if __name__ == "__main__":
unittest.main()
view raw test_example.py hosted with ❤ by GitHub


Basically, what we are doing here in the test:


  • Create a mock for RackspaceNodeDriver class and record list_nodes() call for it; we also make it return two node objects. Actually, we create fake objects using type(), so we don't have to bother with creation of real Node objects which need a way more properties to be set when constructing that we don't care about

  • Use mox's StubOutWithMock routine (please check mox docs for details on that) to stub out libcloud.providers module. Here, in get_driver() we pass lambda because get_driver() returns class itself, not an instance of the class, and using lambda here seems to be more clear than recording __call__() for a class

  • All the standard ReplayAll/VerifyAll mox routines


For the sake of experiment, you can replace actual node_names() method of the Example class with just return ["node1", "node2"]. The assertEqual() will pass, but mox will complain that list_nodes() wasn't called and test will fail.

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete