Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing with Mock

Testing with Mock

A Short talk on testing with Mock. Given at the Memphis Python User group on April 15, 2013

Brad Montgomery

April 15, 2013
Tweet

More Decks by Brad Montgomery

Other Decks in Programming

Transcript

  1. 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
  2. What is Mock? In the Standard Lib as of Python

    3.3+ unittest.mock Monday, April 15, 13
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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