Slide 1

Slide 1 text

Testing with Mock by: Brad Montgomery Monday, April 15, 13

Slide 2

Slide 2 text

Disclaimer Monday, April 15, 13

Slide 3

Slide 3 text

What is Mock? “mock allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.” Monday, April 15, 13

Slide 4

Slide 4 text

What is Mock? In the Standard Lib as of Python 3.3+ unittest.mock Monday, April 15, 13

Slide 5

Slide 5 text

What is Mock? For Python 2.x pip install mock Monday, April 15, 13

Slide 6

Slide 6 text

When is it useful? • Test code that hits External APIS • Test code that’s “expensive” • Test for side effects (Exceptions) • Test code that needs a lot of setup Monday, April 15, 13

Slide 7

Slide 7 text

Typical First Impressions... Monday, April 15, 13

Slide 8

Slide 8 text

Code that hits an API # examples.py - Hit a 3rd-party API from pygithub3 import Github def get_user(username): gh = Github() return gh.users.get(username) Monday, April 15, 13

Slide 9

Slide 9 text

Code that hits an API from mock import call, patch from examples import get_user # test_examples.py - Hit a 3rd-party API def test_get_user(): with patch('examples.Github') as mock_github: mock_gh = mock_github.return_value get_user('foo') # call our function mock_github.assert_has_calls([ call() ]) mock_gh.assert_has_calls([ call.users.get('foo') ]) Monday, April 15, 13

Slide 10

Slide 10 text

Side Effects import requests # examples.py - Side Effects def fetch(): """Asumme you're hitting some API.""" try: response = requests.get('http://example.com') return response.content except requests.HTTPError: # <-- TEST this return None Monday, April 15, 13

Slide 11

Slide 11 text

Side Effects from mock import call, patch from examples import fetch # test_examples.py - Side Effects def test_fetch(): config = { 'get.side_effect': Exception, 'HTTPError': Exception, } with patch('examples.requests', **config) as mock_requests: result = fetch() assert result is None mock_requests.assert_has_calls([ call.get('http://example.com') ]) Monday, April 15, 13

Slide 12

Slide 12 text

Expensive Calls # examples.py - "expensive" calls class User(object): def activities(self): """does expensive DB queries""" # ... return '' def dashboard(self): """groups user activities""" results = [] for activity in self.activities(): # do some stuff results.append(activity) return results Monday, April 15, 13

Slide 13

Slide 13 text

Expensive Calls from mock import Mock from examples import User # test_examples.py - "expensive" calls def test_user_dashboard(): u = User() u.activities = Mock(return_value=['blah']) result = u.dashboard() assert result == ['blah'] assert u.activities.called Monday, April 15, 13

Slide 14

Slide 14 text

Tedious Setup # examples.py - Lots of Setup def list_user_activities(username): # Assume there's an ORM attached to User u = User.objects.get(username=username) # Assume the ``activities`` method returns an iterable # of Activity objects that have a ``title`` attribute. return [a.title for a in u.activities()] Monday, April 15, 13

Slide 15

Slide 15 text

Tedious Setup def test_list_user_activities(): # spec - When specified, Mock will raise an # AttributeError if any attr is not in the spec mock_activity = Mock(spec=['title']) # provide a return value for the ``activities`` method config = {'activities.return_value': [mock_activity]} mock_user_instance = Mock(**config) # Mock User Class (via patch) config = {'objects.get.return_value': mock_user_instance} with patch('examples.User', **config) as mock_user_class: list_user_activities('xavier') mock_user_class.assert_has_calls([ call.objects.get(username='xavier') ]) mock_user_instance.assert_has_calls([ call.activities() ]) Monday, April 15, 13

Slide 16

Slide 16 text

Thanks! Code: http://bit.ly/mock-gist Mock Docs: http://www.voidspace.org.uk/python/mock/ Monday, April 15, 13