Created
October 28, 2015 17:09
-
-
Save proteusvacuum/ab6dbb95ed210fb55ada to your computer and use it in GitHub Desktop.
Python mocks brownbag
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
from mock import MagicMock, call, patch | |
# “mock is a library for testing in Python. It allows you to replace parts of | |
# your system under test with mock objects and make assertions about how they | |
# have been used.” | |
thing = MagicMock(name='thing', return_value=100) | |
print thing() | |
print thing(1, 2, 3) | |
print thing.call_count | |
print thing.called | |
print thing.assert_called_with(1) | |
print thing.assert_called_with(1, 2, 3) | |
print thing.call_args | |
print thing(1, 2, foo='bar') | |
print thing.call_args == call(1, 2, foo='bar') | |
thing.side_effect = Exception("BOOM!") | |
thing() | |
thing.side_effect = lambda *args, **kwargs: (args, kwargs) | |
print thing(1, 2, 3, foo="bar") | |
# foot shooting | |
# tied to implementation | |
# patch | |
# The patch decorators are used for patching objects only within the scope of | |
# the function they decorate. They automatically handle the unpatching for you, | |
# even if exceptions are raised. All of these functions can also be used in with | |
# statements or as class decorators | |
class Thinger(object): | |
def do_thing(self): | |
return 100 | |
with patch('__main__.Thinger') as FakeThinger: | |
"""patch whole objects""" | |
thing = FakeThinger.return_value | |
thing.do_thing.return_value = 12 | |
print Thinger().do_thing() # 12 | |
print type(Thinger()) | |
with patch('__main__.Thinger.do_thing') as fake_thing_doer: | |
"""patch object methods""" | |
fake_thing_doer.return_value = 12 | |
print Thinger().do_thing() | |
print type(Thinger()) | |
class MockThinger(object): | |
def do_thing(self): | |
return 100000 | |
with patch('__main__.Thinger', new=MockThinger): | |
"""use a different object in place of the patched object""" | |
print Thinger().do_thing() # 100000 | |
print type(Thinger()) | |
# Can be used as a method decorator | |
@patch('__main__.Thinger') | |
def test_thinger_does_thing(self, fake_thinger): | |
pass | |
# Can also be used as a class decorator | |
@patch('__main__.Thinger') | |
class ThingerTest(SimpleTestCase): | |
def test_thinger_does_thing(self, FakeThinger): | |
print Thinger().do_thing() | |
# 10000 | |
def test_other_thing_calls_do_thing(self, FakeThinger): | |
ThingerFactory().make_thing_do_thing() | |
self.assertEqual(25, Thing.do_thing.call_count) | |
with patch.object(Thinger, 'do_thing') as fake_thing_doer: | |
fake_thing_doer.return_value = 'foo' | |
print Thinger().do_thing() | |
lookup_table = {'foo': 'bar', 'baz': 'quux'} | |
with patch.dict(lookup_table, {'foo': 'biff'}): | |
print lookup_table['foo'] | |
print lookup_table['baz'] | |
# biff | |
# Where to patch! | |
# =============== | |
# The basic principle is that you patch where an object is | |
# looked up, which is not necessarily the same place as where it is defined | |
# Basically, we patch the object IN THE PLACE INTO WHICH IT HAS BEEN IMPORTED, not where it is defined | |
# >>> Want to test whether 'send_to_elastic' is called | |
# >>> File I am testing: corehq/apps/users/signals.py | |
from corehq.elastic import send_to_elasticsearch | |
def update_user_in_es(sender, couch_user, **kwargs): | |
""" | |
Automatically sync the user to elastic directly on save or delete | |
""" | |
send_to_elasticsearch("users", couch_user.to_json(), | |
delete=couch_user.to_be_deleted()) | |
# >>> in test file | |
@patch('corehq.apps.users.signals.send_to_elasticsearch') | |
def test_send_to_elastic_is_called(self, send_to_es): | |
do_a_thing() | |
self.assertTrue(send_to_es.called) | |
# Patch dates! | |
# No more randomness! | |
from datetime import date | |
with patch('mymodule.date') as mock_date: | |
mock_date.today.return_value = date(2015, 10, 28) | |
mock_date.side_effect = lambda *args, **kw: date(*args, **kw) | |
my_module.do_a_thing_with_today() # doesn't matter when this test is run! | |
assert mymodule.date.today() == date(2010, 10, 8) | |
assert mymodule.date(2009, 6, 8) == date(2009, 6, 8) | |
# Fake Couch | |
# OMG we can fake couch! | |
# can use the @mock_out_couch decorator in util/test_utils.py | |
# ## blanket mocks out couch | |
# e.g. users/tests/test_signals.py | |
# or: | |
# can pass JSON into FakeCouch which returns whatever you want | |
# Homework: learn how to use it | |
# ## OMG mocks r sooo cooool. | |
# Chill: Too many mocks can happen! | |
# General rule is to use mocks across architecture boudaries (e.g. don't care if db is saving properly, | |
# don't want to call external apis) | |
# ## Resources | |
# http://martinfowler.com/articles/mocksArentStubs.html | |
# https://www.youtube.com/watch?v=yFA-FFaEZPo | |
# http://www.voidspace.org.uk/python/mock/patch.html | |
# http://www.voidspace.org.uk/python/mock/examples.html |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment