How to write your own mocking library?
Using metaprogramming in python
About Me
Deepak Narayana Rao
Solution Consultant @ Sahaj Software
@endeepak on www, github , twitter etc
What is mocking?
# Simple unit test case with mocking�def test_place_order_creates_order_in_repository():� order_repository = Mock()� order_service = OrderService(order_repository)� order = Order(product='Python Programming Book', price=300)�� order_service.place_order(order)�� order_repository.create_order.assert_called_once_with(order)
Ingredients for basic mocking
> order_repository = Mock()
> order_repository.create_order(order)
Need a way to create objects which can respond to dynamic methods
> order_repository.create_order.assert_called_once_with(order)
Need a way to inspect calls made to these dynamic methods
Metaprogramming : __getattr__
# Create objects which can respond to dynamic methods�class DynamicGreeter(object):� def __getattr__(self, attr_name):� if attr_name.startswith('greet_'):� def greeter():� print('Hello ' + attr_name.replace('greet_', ''))� return greeter� else:� raise Exception('Could not understand method ' + attr_name)
greeter = DynamicGreeter()�greeter.greet_python() # Output > Hello python�greeter.greet_everyone() # Output > Hello everyone�greeter.something_else() # Exception: Could not understand method something_else
Metaprogramming : __call__
# Ability to intercept calls
class CallableObject(object):� def __init__(self, name):� self.name = name�� def __call__(self, *args, **kwargs):� print(self.name, 'received call', args, kwargs)�
callable_object = CallableObject('awesome_method')�callable_object('hello')�# Output > awesome_method received call ('hello',) {}��callable_object('hello', to='world')�# Output > awesome_method received call ('hello',) {'to': 'world'}
Let us write our own mocking library
SuperMock
Spock V1
class Call(object):� def __init__(self, args, kwargs):� self.args = args� self.kwargs = kwargs���class SpyMethod(object):� def __init__(self, name):� self.name = name� self.calls = []�� def __call__(self, *args, **kwargs):� self.calls.append(Call(args, kwargs))
class Spock(object):� def __getattr__(self, attr_name):� spy_method = SpyMethod(attr_name)� setattr(self, attr_name, spy_method)� return spy_method
Spock V1 : Usage
from spock import Spock�from order_service import OrderService, Order��def test_place_order_creates_order_in_repository():� order_repository = Spock()� order_service = OrderService(order_repository)� order = Order(product='Python Programming Book', price=300)�� order_service.place_order(order)�� assert len(order_repository.create_order.calls) == 1� assert len(order_repository.create_order.calls[0].args) == 1� assert order_repository.create_order.calls[0].args[0] == order
Spock V1.1 - Easier way to assert
class Call(object):� # ...� def received(self, *args, **kwargs):� return (self.args, self.kwargs) == (args, kwargs)
class SpyMethod(object):� # ...� def was_called_with(self, *args, **kwargs):� for call in self.calls:� if(call.received(*args, **kwargs)):� return True� return False
# test_place_order_creates_order_in_repository
assert order_repository.create_order.was_called_with(order)
Spock V1.2 - SpyMethod to return value
Coming soon...
Spock V1.3 - SpyMethod to raise exception
Coming soon...
Next Steps : Add more features to Spock
Thank you !