├── MANIFEST.in ├── .coveragerc ├── .travis.yml ├── .gitignore ├── LICENSE ├── setup.py ├── README.md ├── httmock.py └── tests.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = tests.py 3 | source = httmock 4 | 5 | [report] 6 | exclude_lines = 7 | pragma: no cover 8 | raise NotImplementedError 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | arch: 3 | - amd64 4 | - ppc64le 5 | language: python 6 | python: 7 | - 2.7 8 | - pypy 9 | - 3.4 10 | - 3.5 11 | - 3.6 12 | - 3.7 13 | - 3.8 14 | jobs: 15 | exclude: 16 | - arch: ppc64le 17 | python: pypy 18 | install: 19 | - "pip install requests" 20 | script: nosetests 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Patryk Zawadzki 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | import os 5 | 6 | DESCRIPTION = open( 7 | os.path.join(os.path.dirname(__file__), 'README.md')).read().strip() 8 | 9 | setup( 10 | name='httmock', 11 | version='1.4.0', 12 | description='A mocking library for requests.', 13 | author='Patryk Zawadzki', 14 | author_email='patrys@room-303.com', 15 | url='https://github.com/patrys/httmock', 16 | py_modules=['httmock'], 17 | keywords=['requests', 'testing', 'mock'], 18 | classifiers=[ 19 | 'Programming Language :: Python :: 2', 20 | 'Programming Language :: Python :: 3', 21 | 'Intended Audience :: Developers', 22 | 'License :: OSI Approved :: Apache Software License', 23 | 'Topic :: Software Development :: Testing', 24 | 'Operating System :: OS Independent'], 25 | install_requires=['requests >= 1.0.0'], 26 | license='Apache-2.0', 27 | long_description=DESCRIPTION, 28 | long_description_content_type='text/markdown', 29 | test_suite='tests') 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | httmock 2 | ======= 3 | 4 | A mocking library for `requests` for Python 2.7 and 3.4+. 5 | 6 | Installation 7 | ------------ 8 | 9 | pip install httmock 10 | 11 | Or, if you are a Gentoo user: 12 | 13 | emerge dev-python/httmock 14 | 15 | Usage 16 | ----- 17 | You can use it to mock third-party APIs and test libraries that use `requests` internally, conditionally using mocked replies with the `urlmatch` decorator: 18 | 19 | ```python 20 | from httmock import urlmatch, HTTMock 21 | import requests 22 | 23 | @urlmatch(netloc=r'(.*\.)?google\.com$') 24 | def google_mock(url, request): 25 | return 'Feeling lucky, punk?' 26 | 27 | with HTTMock(google_mock): 28 | r = requests.get('http://google.com/') 29 | print r.content # 'Feeling lucky, punk?' 30 | ``` 31 | 32 | The `all_requests` decorator doesn't conditionally block real requests. If you return a dictionary, it will map to the `requests.Response` object returned: 33 | 34 | ```python 35 | from httmock import all_requests, HTTMock 36 | import requests 37 | 38 | @all_requests 39 | def response_content(url, request): 40 | return {'status_code': 200, 41 | 'content': 'Oh hai'} 42 | 43 | with HTTMock(response_content): 44 | r = requests.get('https://foo_bar') 45 | 46 | print r.status_code 47 | print r.content 48 | ``` 49 | 50 | If you pass in `Set-Cookie` headers, `requests.Response.cookies` will contain the values. You can also use `response` method directly instead of returning a dict: 51 | 52 | ```python 53 | from httmock import all_requests, response, HTTMock 54 | import requests 55 | 56 | @all_requests 57 | def response_content(url, request): 58 | headers = {'content-type': 'application/json', 59 | 'Set-Cookie': 'foo=bar;'} 60 | content = {'message': 'API rate limit exceeded'} 61 | return response(403, content, headers, None, 5, request) 62 | 63 | with HTTMock(response_content): 64 | r = requests.get('https://api.github.com/users/whatever') 65 | 66 | print r.json().get('message') 67 | print r.cookies['foo'] 68 | ``` 69 | -------------------------------------------------------------------------------- /httmock.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import datetime 3 | from requests import cookies 4 | import json 5 | import re 6 | import requests 7 | from requests import structures, utils 8 | import sys 9 | try: 10 | import urlparse 11 | except ImportError: 12 | import urllib.parse as urlparse 13 | 14 | if sys.version_info >= (3, 0, 0): 15 | from io import BytesIO 16 | else: 17 | from StringIO import StringIO as BytesIO 18 | 19 | 20 | binary_type = bytes 21 | if sys.version_info >= (3, 0, 0): 22 | text_type = str 23 | else: 24 | text_type = unicode # noqa 25 | 26 | 27 | class Headers(object): 28 | def __init__(self, res): 29 | self.headers = res.headers 30 | 31 | def get_all(self, name, failobj=None): 32 | return self.getheaders(name) 33 | 34 | def getheaders(self, name): 35 | return [self.headers.get(name)] 36 | 37 | 38 | def response(status_code=200, content='', headers=None, reason=None, elapsed=0, 39 | request=None, stream=False, http_vsn=11): 40 | res = requests.Response() 41 | res.status_code = status_code 42 | if isinstance(content, (dict, list)): 43 | content = json.dumps(content).encode('utf-8') 44 | if isinstance(content, text_type): 45 | content = content.encode('utf-8') 46 | res._content = content 47 | res._content_consumed = content 48 | res.headers = structures.CaseInsensitiveDict(headers or {}) 49 | res.encoding = utils.get_encoding_from_headers(res.headers) 50 | res.reason = reason 51 | res.elapsed = datetime.timedelta(elapsed) 52 | res.request = request 53 | if hasattr(request, 'url'): 54 | res.url = request.url 55 | if isinstance(request.url, bytes): 56 | res.url = request.url.decode('utf-8') 57 | if 'set-cookie' in res.headers: 58 | res.cookies.extract_cookies(cookies.MockResponse(Headers(res)), 59 | cookies.MockRequest(request)) 60 | if stream: 61 | res.raw = BytesIO(content) 62 | else: 63 | res.raw = BytesIO(b'') 64 | res.raw.version = http_vsn 65 | 66 | # normally this closes the underlying connection, 67 | # but we have nothing to free. 68 | res.close = lambda *args, **kwargs: None 69 | 70 | return res 71 | 72 | 73 | def all_requests(func): 74 | @wraps(func) 75 | def inner(*args, **kwargs): 76 | return func(*args, **kwargs) 77 | return inner 78 | 79 | 80 | def urlmatch(scheme=None, netloc=None, path=None, method=None, query=None): 81 | def decorator(func): 82 | @wraps(func) 83 | def inner(self_or_url, url_or_request, *args, **kwargs): 84 | if isinstance(self_or_url, urlparse.SplitResult): 85 | url = self_or_url 86 | request = url_or_request 87 | else: 88 | url = url_or_request 89 | request = args[0] 90 | if scheme is not None and scheme != url.scheme: 91 | return 92 | if netloc is not None and not re.match(netloc, url.netloc): 93 | return 94 | if path is not None and not re.match(path, url.path): 95 | return 96 | if query is not None and not re.match(query, url.query): 97 | return 98 | if method is not None and method.upper() != request.method: 99 | return 100 | return func(self_or_url, url_or_request, *args, **kwargs) 101 | return inner 102 | return decorator 103 | 104 | 105 | def handler_init_call(handler): 106 | setattr(handler, 'call', { 107 | 'count': 0, 108 | 'called': False, 109 | 'requests': [] 110 | }) 111 | 112 | 113 | def handler_clean_call(handler): 114 | if hasattr(handler, 'call'): 115 | handler.call.update({ 116 | 'count': 0, 117 | 'called': False, 118 | 'requests': [] 119 | }) 120 | 121 | 122 | def handler_called(handler, *args, **kwargs): 123 | try: 124 | return handler(*args, **kwargs) 125 | finally: 126 | handler.call['count'] += 1 127 | handler.call['called'] = True 128 | handler.call['requests'].append(args[1]) 129 | 130 | def remember_called(func): 131 | handler_init_call(func) 132 | 133 | @wraps(func) 134 | def inner(*args, **kwargs): 135 | return handler_called(func, *args, **kwargs) 136 | return inner 137 | 138 | 139 | def first_of(handlers, *args, **kwargs): 140 | for handler in handlers: 141 | res = handler(*args, **kwargs) 142 | if res is not None: 143 | return res 144 | 145 | 146 | class HTTMock(object): 147 | """ 148 | Acts as a context manager to allow mocking 149 | """ 150 | STATUS_CODE = 200 151 | 152 | def __init__(self, *handlers): 153 | self.handlers = handlers 154 | 155 | def __enter__(self): 156 | self._real_session_send = requests.Session.send 157 | self._real_session_prepare_request = requests.Session.prepare_request 158 | 159 | for handler in self.handlers: 160 | handler_clean_call(handler) 161 | 162 | def _fake_send(session, request, **kwargs): 163 | response = self.intercept(request, **kwargs) 164 | 165 | if isinstance(response, requests.Response): 166 | # this is pasted from requests to handle redirects properly: 167 | kwargs.setdefault('stream', session.stream) 168 | kwargs.setdefault('verify', session.verify) 169 | kwargs.setdefault('cert', session.cert) 170 | kwargs.setdefault('proxies', session.proxies) 171 | 172 | allow_redirects = kwargs.pop('allow_redirects', True) 173 | stream = kwargs.get('stream') 174 | timeout = kwargs.get('timeout') 175 | verify = kwargs.get('verify') 176 | cert = kwargs.get('cert') 177 | proxies = kwargs.get('proxies') 178 | 179 | gen = session.resolve_redirects( 180 | response, 181 | request, 182 | stream=stream, 183 | timeout=timeout, 184 | verify=verify, 185 | cert=cert, 186 | proxies=proxies) 187 | 188 | history = [resp for resp in gen] if allow_redirects else [] 189 | 190 | if history: 191 | history.insert(0, response) 192 | response = history.pop() 193 | response.history = tuple(history) 194 | 195 | session.cookies.update(response.cookies) 196 | 197 | return response 198 | 199 | return self._real_session_send(session, request, **kwargs) 200 | 201 | def _fake_prepare_request(session, request): 202 | """ 203 | Fake this method so the `PreparedRequest` objects contains 204 | an attribute `original` of the original request. 205 | """ 206 | prep = self._real_session_prepare_request(session, request) 207 | prep.original = request 208 | return prep 209 | 210 | requests.Session.send = _fake_send 211 | requests.Session.prepare_request = _fake_prepare_request 212 | 213 | return self 214 | 215 | def __exit__(self, exc_type, exc_val, exc_tb): 216 | requests.Session.send = self._real_session_send 217 | requests.Session.prepare_request = self._real_session_prepare_request 218 | 219 | def intercept(self, request, **kwargs): 220 | url = urlparse.urlsplit(request.url) 221 | res = first_of(self.handlers, url, request) 222 | 223 | if isinstance(res, requests.Response): 224 | return res 225 | elif isinstance(res, dict): 226 | return response(res.get('status_code'), 227 | res.get('content'), 228 | res.get('headers'), 229 | res.get('reason'), 230 | res.get('elapsed', 0), 231 | request, 232 | stream=kwargs.get('stream', False), 233 | http_vsn=res.get('http_vsn', 11)) 234 | elif isinstance(res, (text_type, binary_type)): 235 | return response(content=res, stream=kwargs.get('stream', False)) 236 | elif res is None: 237 | return None 238 | else: 239 | raise TypeError( 240 | "Dont know how to handle response of type {0}".format(type(res))) 241 | 242 | 243 | def with_httmock(*handlers): 244 | mock = HTTMock(*handlers) 245 | 246 | def decorator(func): 247 | @wraps(func) 248 | def inner(*args, **kwargs): 249 | with mock: 250 | return func(*args, **kwargs) 251 | return inner 252 | return decorator 253 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | import unittest 4 | 5 | from httmock import (all_requests, response, urlmatch, with_httmock, HTTMock, 6 | remember_called, text_type, binary_type) 7 | 8 | 9 | @urlmatch(scheme='swallow') 10 | def unmatched_scheme(url, request): 11 | raise AssertionError('This is outrageous') 12 | 13 | 14 | @urlmatch(path=r'^never$') 15 | def unmatched_path(url, request): 16 | raise AssertionError('This is outrageous') 17 | 18 | 19 | @urlmatch(method='post') 20 | def unmatched_method(url, request): 21 | raise AssertionError('This is outrageous') 22 | 23 | 24 | @urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$') 25 | def google_mock(url, request): 26 | return 'Hello from Google' 27 | 28 | 29 | @urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$') 30 | @remember_called 31 | def google_mock_count(url, request): 32 | return 'Hello from Google' 33 | 34 | 35 | @urlmatch(scheme='http', netloc=r'(.*\.)?facebook\.com$') 36 | def facebook_mock(url, request): 37 | return 'Hello from Facebook' 38 | 39 | 40 | @urlmatch(scheme='http', netloc=r'(.*\.)?facebook\.com$') 41 | @remember_called 42 | def facebook_mock_count(url, request): 43 | return 'Hello from Facebook' 44 | 45 | @urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$', method='POST') 46 | @remember_called 47 | def google_mock_store_requests(url, request): 48 | return 'Posting at Google' 49 | 50 | 51 | @all_requests 52 | def charset_utf8(url, request): 53 | return { 54 | 'content': u'Motörhead'.encode('utf-8'), 55 | 'status_code': 200, 56 | 'headers': { 57 | 'Content-Type': 'text/plain; charset=utf-8' 58 | } 59 | } 60 | 61 | 62 | def any_mock(url, request): 63 | return 'Hello from %s' % (url.netloc,) 64 | 65 | 66 | def dict_any_mock(url, request): 67 | return { 68 | 'content': 'Hello from %s' % (url.netloc,), 69 | 'status_code': 200, 70 | 'http_vsn': 10, 71 | } 72 | 73 | 74 | def example_400_response(url, response): 75 | r = requests.Response() 76 | r.status_code = 400 77 | r._content = b'Bad request.' 78 | return r 79 | 80 | 81 | class MockTest(unittest.TestCase): 82 | 83 | def test_return_type(self): 84 | with HTTMock(any_mock): 85 | r = requests.get('http://domain.com/') 86 | self.assertTrue(isinstance(r, requests.Response)) 87 | self.assertTrue(isinstance(r.content, binary_type)) 88 | self.assertTrue(isinstance(r.text, text_type)) 89 | 90 | def test_scheme_fallback(self): 91 | with HTTMock(unmatched_scheme, any_mock): 92 | r = requests.get('http://example.com/') 93 | self.assertEqual(r.content, b'Hello from example.com') 94 | 95 | def test_path_fallback(self): 96 | with HTTMock(unmatched_path, any_mock): 97 | r = requests.get('http://example.com/') 98 | self.assertEqual(r.content, b'Hello from example.com') 99 | 100 | def test_method_fallback(self): 101 | with HTTMock(unmatched_method, any_mock): 102 | r = requests.get('http://example.com/') 103 | self.assertEqual(r.content, b'Hello from example.com') 104 | 105 | def test_netloc_fallback(self): 106 | with HTTMock(google_mock, facebook_mock): 107 | r = requests.get('http://google.com/') 108 | self.assertEqual(r.content, b'Hello from Google') 109 | with HTTMock(google_mock, facebook_mock): 110 | r = requests.get('http://facebook.com/') 111 | self.assertEqual(r.content, b'Hello from Facebook') 112 | 113 | def test_400_response(self): 114 | with HTTMock(example_400_response): 115 | r = requests.get('http://example.com/') 116 | self.assertEqual(r.status_code, 400) 117 | self.assertEqual(r.content, b'Bad request.') 118 | 119 | def test_real_request_fallback(self): 120 | with HTTMock(any_mock): 121 | with HTTMock(google_mock, facebook_mock): 122 | r = requests.get('http://example.com/') 123 | self.assertEqual(r.status_code, 200) 124 | self.assertEqual(r.content, b'Hello from example.com') 125 | 126 | def test_invalid_intercept_response_raises_value_error(self): 127 | @all_requests 128 | def response_content(url, request): 129 | return -1 130 | with HTTMock(response_content): 131 | self.assertRaises(TypeError, requests.get, 'http://example.com/') 132 | 133 | def test_encoding_from_contenttype(self): 134 | with HTTMock(charset_utf8): 135 | r = requests.get('http://example.com/') 136 | self.assertEqual(r.encoding, 'utf-8') 137 | self.assertEqual(r.text, u'Motörhead') 138 | self.assertEqual(r.content, r.text.encode('utf-8')) 139 | 140 | def test_has_raw_version(self): 141 | with HTTMock(any_mock): 142 | r = requests.get('http://example.com') 143 | self.assertEqual(r.raw.version, 11) 144 | with HTTMock(dict_any_mock): 145 | r = requests.get('http://example.com') 146 | self.assertEqual(r.raw.version, 10) 147 | 148 | class DecoratorTest(unittest.TestCase): 149 | 150 | @with_httmock(any_mock) 151 | def test_decorator(self): 152 | r = requests.get('http://example.com/') 153 | self.assertEqual(r.content, b'Hello from example.com') 154 | 155 | @with_httmock(any_mock) 156 | def test_iter_lines(self): 157 | r = requests.get('http://example.com/') 158 | self.assertEqual(list(r.iter_lines()), 159 | [b'Hello from example.com']) 160 | 161 | 162 | class AllRequestsDecoratorTest(unittest.TestCase): 163 | 164 | def test_all_requests_response(self): 165 | @all_requests 166 | def response_content(url, request): 167 | return {'status_code': 200, 'content': 'Oh hai'} 168 | with HTTMock(response_content): 169 | r = requests.get('https://example.com/') 170 | self.assertEqual(r.status_code, 200) 171 | self.assertEqual(r.content, b'Oh hai') 172 | 173 | def test_all_str_response(self): 174 | @all_requests 175 | def response_content(url, request): 176 | return 'Hello' 177 | with HTTMock(response_content): 178 | r = requests.get('https://example.com/') 179 | self.assertEqual(r.content, b'Hello') 180 | 181 | 182 | class AllRequestsMethodDecoratorTest(unittest.TestCase): 183 | @all_requests 184 | def response_content(self, url, request): 185 | return {'status_code': 200, 'content': 'Oh hai'} 186 | 187 | def test_all_requests_response(self): 188 | with HTTMock(self.response_content): 189 | r = requests.get('https://example.com/') 190 | self.assertEqual(r.status_code, 200) 191 | self.assertEqual(r.content, b'Oh hai') 192 | 193 | @all_requests 194 | def string_response_content(self, url, request): 195 | return 'Hello' 196 | 197 | def test_all_str_response(self): 198 | with HTTMock(self.string_response_content): 199 | r = requests.get('https://example.com/') 200 | self.assertEqual(r.content, b'Hello') 201 | 202 | 203 | class UrlMatchMethodDecoratorTest(unittest.TestCase): 204 | @urlmatch(netloc=r'(.*\.)?google\.com$', path=r'^/$') 205 | def google_mock(self, url, request): 206 | return 'Hello from Google' 207 | 208 | @urlmatch(scheme='http', netloc=r'(.*\.)?facebook\.com$') 209 | def facebook_mock(self, url, request): 210 | return 'Hello from Facebook' 211 | 212 | @urlmatch(query=r'.*page=test') 213 | def query_page_mock(self, url, request): 214 | return 'Hello from test page' 215 | 216 | def test_netloc_fallback(self): 217 | with HTTMock(self.google_mock, facebook_mock): 218 | r = requests.get('http://google.com/') 219 | self.assertEqual(r.content, b'Hello from Google') 220 | with HTTMock(self.google_mock, facebook_mock): 221 | r = requests.get('http://facebook.com/') 222 | self.assertEqual(r.content, b'Hello from Facebook') 223 | 224 | def test_query(self): 225 | with HTTMock(self.query_page_mock, self.google_mock): 226 | r = requests.get('http://google.com/?page=test') 227 | r2 = requests.get('http://google.com/') 228 | self.assertEqual(r.content, b'Hello from test page') 229 | self.assertEqual(r2.content, b'Hello from Google') 230 | 231 | 232 | class ResponseTest(unittest.TestCase): 233 | 234 | content = {'name': 'foo', 'ipv4addr': '127.0.0.1'} 235 | content_list = list(content.keys()) 236 | 237 | def test_response_auto_json(self): 238 | r = response(0, self.content) 239 | self.assertTrue(isinstance(r.content, binary_type)) 240 | self.assertTrue(isinstance(r.text, text_type)) 241 | self.assertEqual(r.json(), self.content) 242 | r = response(0, self.content_list) 243 | self.assertEqual(r.json(), self.content_list) 244 | 245 | def test_response_status_code(self): 246 | r = response(200) 247 | self.assertEqual(r.status_code, 200) 248 | 249 | def test_response_headers(self): 250 | r = response(200, None, {'Content-Type': 'application/json'}) 251 | self.assertEqual(r.headers['content-type'], 'application/json') 252 | 253 | def test_response_raw_version(self): 254 | r = response(200, None, {'Content-Type': 'application/json'}, 255 | http_vsn=10) 256 | self.assertEqual(r.raw.version, 10) 257 | 258 | def test_response_cookies(self): 259 | @all_requests 260 | def response_content(url, request): 261 | return response(200, 'Foo', {'Set-Cookie': 'foo=bar;'}, 262 | request=request) 263 | with HTTMock(response_content): 264 | r = requests.get('https://example.com/') 265 | self.assertEqual(len(r.cookies), 1) 266 | self.assertTrue('foo' in r.cookies) 267 | self.assertEqual(r.cookies['foo'], 'bar') 268 | 269 | def test_response_session_cookies(self): 270 | @all_requests 271 | def response_content(url, request): 272 | return response(200, 'Foo', {'Set-Cookie': 'foo=bar;'}, 273 | request=request) 274 | session = requests.Session() 275 | with HTTMock(response_content): 276 | r = session.get('https://foo_bar') 277 | self.assertEqual(len(r.cookies), 1) 278 | self.assertTrue('foo' in r.cookies) 279 | self.assertEqual(r.cookies['foo'], 'bar') 280 | self.assertEqual(len(session.cookies), 1) 281 | self.assertTrue('foo' in session.cookies) 282 | self.assertEqual(session.cookies['foo'], 'bar') 283 | 284 | def test_session_persistent_cookies(self): 285 | session = requests.Session() 286 | with HTTMock(lambda u, r: response(200, 'Foo', {'Set-Cookie': 'foo=bar;'}, request=r)): 287 | session.get('https://foo_bar') 288 | with HTTMock(lambda u, r: response(200, 'Baz', {'Set-Cookie': 'baz=qux;'}, request=r)): 289 | session.get('https://baz_qux') 290 | self.assertEqual(len(session.cookies), 2) 291 | self.assertTrue('foo' in session.cookies) 292 | self.assertEqual(session.cookies['foo'], 'bar') 293 | self.assertTrue('baz' in session.cookies) 294 | self.assertEqual(session.cookies['baz'], 'qux') 295 | 296 | def test_python_version_encoding_differences(self): 297 | # Previous behavior would result in this test failing in Python3 due 298 | # to how requests checks for utf-8 JSON content in requests.utils with: 299 | # 300 | # TypeError: Can't convert 'bytes' object to str implicitly 301 | @all_requests 302 | def get_mock(url, request): 303 | return {'content': self.content, 304 | 'headers': {'content-type': 'application/json'}, 305 | 'status_code': 200, 306 | 'elapsed': 5} 307 | 308 | with HTTMock(get_mock): 309 | response = requests.get('http://example.com/') 310 | self.assertEqual(self.content, response.json()) 311 | 312 | def test_mock_redirect(self): 313 | @urlmatch(netloc='example.com') 314 | def get_mock(url, request): 315 | return {'status_code': 302, 316 | 'headers': {'Location': 'http://google.com/'}} 317 | 318 | with HTTMock(get_mock, google_mock): 319 | response = requests.get('http://example.com/') 320 | self.assertEqual(len(response.history), 1) 321 | self.assertEqual(response.content, b'Hello from Google') 322 | 323 | 324 | class StreamTest(unittest.TestCase): 325 | @with_httmock(any_mock) 326 | def test_stream_request(self): 327 | r = requests.get('http://domain.com/', stream=True) 328 | self.assertEqual(r.raw.read(), b'Hello from domain.com') 329 | 330 | @with_httmock(dict_any_mock) 331 | def test_stream_request_with_dict_mock(self): 332 | r = requests.get('http://domain.com/', stream=True) 333 | self.assertEqual(r.raw.read(), b'Hello from domain.com') 334 | 335 | @with_httmock(any_mock) 336 | def test_non_stream_request(self): 337 | r = requests.get('http://domain.com/') 338 | self.assertEqual(r.raw.read(), b'') 339 | 340 | 341 | class RememberCalledTest(unittest.TestCase): 342 | 343 | @staticmethod 344 | def several_calls(count, method, *args, **kwargs): 345 | results = [] 346 | for _ in range(count): 347 | results.append(method(*args, **kwargs)) 348 | return results 349 | 350 | def test_several_calls(self): 351 | with HTTMock(google_mock_count, facebook_mock_count): 352 | results = self.several_calls( 353 | 3, requests.get, 'http://facebook.com/') 354 | 355 | self.assertTrue(facebook_mock_count.call['called']) 356 | self.assertEqual(facebook_mock_count.call['count'], 3) 357 | 358 | self.assertFalse(google_mock_count.call['called']) 359 | self.assertEqual(google_mock_count.call['count'], 0) 360 | 361 | for r in results: 362 | self.assertEqual(r.content, b'Hello from Facebook') 363 | 364 | # Negative case: cleanup call data 365 | with HTTMock(facebook_mock_count): 366 | results = self.several_calls( 367 | 1, requests.get, 'http://facebook.com/') 368 | 369 | self.assertEqual(facebook_mock_count.call['count'], 1) 370 | 371 | @with_httmock(google_mock_count, facebook_mock_count) 372 | def test_several_call_decorated(self): 373 | results = self.several_calls(3, requests.get, 'http://facebook.com/') 374 | 375 | self.assertTrue(facebook_mock_count.call['called']) 376 | self.assertEqual(facebook_mock_count.call['count'], 3) 377 | 378 | self.assertFalse(google_mock_count.call['called']) 379 | self.assertEqual(google_mock_count.call['count'], 0) 380 | 381 | for r in results: 382 | self.assertEqual(r.content, b'Hello from Facebook') 383 | 384 | self.several_calls(1, requests.get, 'http://facebook.com/') 385 | self.assertEqual(facebook_mock_count.call['count'], 4) 386 | 387 | def test_store_several_requests(self): 388 | with HTTMock(google_mock_store_requests): 389 | payload = {"query": "foo"} 390 | requests.post('http://google.com', data=payload) 391 | 392 | self.assertTrue(google_mock_store_requests.call['called']) 393 | self.assertEqual(google_mock_store_requests.call['count'], 1) 394 | request = google_mock_store_requests.call['requests'][0] 395 | self.assertEqual(request.body, 'query=foo') 396 | --------------------------------------------------------------------------------