RkBlog

Hardware, programming and astronomy tutorials and reviews.

Aiding tests with Ludibrio stubs and mocks

Mocking and stubbing models and functions with Ludibrio

In tests we assume some input data and check if the output is equal to what we expect it to be. Sometimes is impossible to set the initial data as the code fetches some data from a third party API, or creates some data on its own (like random data). To aid testing of such code we can use ludibrio. It can mock or stub existing classes or functions making them work as we program it.

Installation is typical - pip install ludibrio. The library allows you to stub objects and define how it should behave. For example random.randint will return a random int from the given range. We don't know what it will be. We can stub it with ludibrio and set one given value it should return. Basing on that we could write a test that could expect a correct output for the stubbed data.

Here is a basic example:
import random


def make_magic_number():
  number = random.randint(1, 100000)
  # some magic we would need to test
  return '%010d' % number
We get a random number and then return it in a given format (assume it's something complex). As the number is random it's hart do test it. But we can use ludibrio like so:
from ludibrio import Stub
import random


def make_magic_number():
  number = random.randint(1, 100000)
  # some magic we would need to test
  return '%010d' % number

with Stub() as random.randint:
  random.randint(1, 100000) >> 5

print make_magic_number()

The Stub() will cause random.randint(1, 100000) (and only such call) to always return 5. The output starts to be predictable - and testable.

More practical case would be external APIs like JSON, XML or other data fetching and processing. Data fetched from the API changes so we can't test for given data, value in the processed output. With ludibrio we can stub something so that it would return our test data. For example:

# -*- coding: utf-8 -*-
import json
from ludibrio import Stub
import urllib2

url = 'http://www.flickr.com/services/rest/?method=flickr.photos.search&format=json&text=python&api_key=API_KEY'

def get_python_images(url):
  handle = urllib2.urlopen(url)
  data = handle.read()
  # a bit of black magic that could use some tests
  data = data.replace('jsonFlickrApi(', '')[:-1]
  parsed_data = json.loads(data)
  return parsed_data

fake_response = 'jsonFlickrApi({"photos":{"page":1, "pages":666, "perpage":66, "total":"6666", "photo":[{"id":"123", "owner":"1111111@N06", "secret":"abcabc", "server":"1", "farm":1, "title":"Test Python", "ispublic":1, "isfriend":0, "isfamily":0}]}, "stat":"ok"})'

with Stub() as urllib2.urlopen:
  urllib2.urlopen(url).read() >> fake_response

print get_python_images(url)
In this example urllib2.urlopen will not call the Flick API but it will return our test data set. As it's fixed we can test if the parsed output contains what it should contain.

Ludibrio is very handy library. In Django tests it can be an aid in tests of management command or similar code using external data sources (APIs). It also can be useful if we want to test one part of code at a time (when a tested function calls a function that calls a function and so on) stubbing one of called methods, functions etc.

RkBlog

2 September 2012;

Comment article