Skip to content

Commit e9316e8

Browse files
committed
Standardize mock_response_factory usage in test_base.py and update documentation
- Updated test_base.py to use mock_response_factory pattern - Enhanced docs/DEVELOPMENT.md with detailed examples for mock_response_factory usage - Documented both standard response pattern and parameter validation pattern
1 parent ed7fb89 commit e9316e8

File tree

3 files changed

+106
-44
lines changed

3 files changed

+106
-44
lines changed

docs/DEVELOPMENT.md

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,27 +195,81 @@ All resource mocks are in the root [conftest.py](tests/conftest.py).
195195

196196
### Response Mocking
197197

198-
The test suite uses the `mock_response_factory` fixture to create consistent,
199-
configurable mock responses:
198+
The test suite uses the `mock_response_factory` fixture from `tests/conftest.py`
199+
to create consistent, configurable mock responses. This is the required pattern
200+
for all tests that need to mock HTTP responses.
201+
202+
#### Standard Mock Response Pattern
203+
204+
```python
205+
def test_some_endpoint(resource, mock_oauth_session, mock_response_factory):
206+
"""Test description."""
207+
# Define expected data first
208+
expected_data = {"data": "test"}
209+
210+
# Create mock response using the factory
211+
mock_response = mock_response_factory(200, expected_data)
212+
213+
# Assign to oauth.request.return_value
214+
resource.oauth.request.return_value = mock_response
215+
216+
# Call the method under test
217+
result = resource.some_method()
218+
219+
# Assert against expected_data, not mock_response.json.return_value
220+
assert result == expected_data
221+
```
222+
223+
#### Response Factory Examples
200224

201225
```python
202-
# Creating a mock response with status code and data
226+
# Success response with data
203227
mock_response = mock_response_factory(200, {"data": "test"})
204228

205-
# Creating a mock response with additional headers
229+
# Response with custom headers
206230
mock_response = mock_response_factory(
207231
status_code=200,
208232
json_data={"data": "test"},
209233
headers={"custom-header": "value"}
210234
)
211235

212-
# Creating a delete response with no content (204)
236+
# Delete/no content response (204)
213237
mock_response = mock_response_factory(204)
238+
239+
# Error response
240+
mock_response = mock_response_factory(
241+
400,
242+
{"errors": [{"errorType": "validation", "message": "Error message"}]}
243+
)
244+
245+
# Non-JSON response (XML)
246+
mock_response = mock_response_factory(
247+
200,
248+
headers={"content-type": "application/vnd.garmin.tcx+xml"},
249+
content_type="application/vnd.garmin.tcx+xml"
250+
)
251+
mock_response.text = "<xml>content</xml>"
252+
```
253+
254+
#### Parameter Validation Pattern
255+
256+
For tests that only need to verify parameter validation or endpoint construction
257+
(not response handling), it's acceptable to use the following alternative
258+
pattern:
259+
260+
```python
261+
def test_validation(resource):
262+
"""Test parameter validation."""
263+
resource._make_request = Mock()
264+
resource.some_method(param="value")
265+
resource._make_request.assert_called_once_with(
266+
"endpoint/path", params={"param": "value"}, user_id="-", debug=False
267+
)
214268
```
215269

216270
This approach provides a clean, standardized way to create mock responses with
217-
the desired status code, data, and headers. All test files should use this
218-
factory method rather than manually configuring mock responses.
271+
the desired status code, data, and headers. All test files must use one of these
272+
patterns.
219273

220274
## OAuth Callback Implementation
221275

standardize_mock_responses.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@ def fix_mock_response_usage(match):
114114
return test_func
115115

116116
content = re.sub(pattern5, fix_mock_response_usage, content, flags=re.DOTALL)
117-
117+
118118
# Pattern 6: Replace assert result == mock_response.json.return_value with assert result == expected_data
119-
pattern6 = r'([ \t]*assert\s+.*?==\s*)mock_response\.json\.return_value'
120-
119+
pattern6 = r"([ \t]*assert\s+.*?==\s*)mock_response\.json\.return_value"
120+
121121
def fix_assertions(match):
122122
before_part = match.group(1)
123123
return f"{before_part}mock_response.json()"
124-
124+
125125
content = re.sub(pattern6, fix_assertions, content)
126126

127127
# If no changes were made, return False
@@ -143,55 +143,57 @@ def find_files_to_transform() -> List[Tuple[str, bool]]:
143143
if len(sys.argv) > 1 and not sys.argv[1].startswith("--"):
144144
test_dir = Path(sys.argv[1])
145145
print(f"Searching in directory: {test_dir}")
146-
146+
147147
result = []
148148
verbose = "--verbose" in sys.argv
149-
149+
150150
for root, _, files in os.walk(test_dir):
151151
for file in files:
152152
if file.endswith(".py") and file.startswith("test_"):
153153
file_path = os.path.join(root, file)
154-
154+
155155
# First check for any mention of mock_response
156156
has_mock_response = bool(find_pattern_in_file(file_path, r"mock_response"))
157-
157+
158158
if not has_mock_response:
159159
continue
160-
160+
161161
# More detailed patterns
162162
# Check if file uses mock_response directly as parameter
163163
uses_mock_response = bool(
164164
find_pattern_in_file(
165165
file_path, r"def\s+test_\w+\([^)]*,\s*mock_response\s*[,)]"
166166
)
167167
)
168-
168+
169169
# Check if file directly manipulates mock_response
170170
manipulates_mock_response = bool(
171171
find_pattern_in_file(
172172
file_path, r"mock_response\.(json\.return_value|status_code)\s*="
173173
)
174174
)
175-
175+
176176
# Check if file uses mock_response.json.return_value in assertions
177177
uses_return_value_in_assertions = bool(
178178
find_pattern_in_file(
179179
file_path, r"assert\s+.*?=.*?mock_response\.json\.return_value"
180180
)
181181
)
182-
182+
183183
# Check if file uses mock_response_factory
184184
uses_factory = bool(
185185
find_pattern_in_file(file_path, r"mock_response\s*=\s*mock_response_factory")
186186
)
187-
187+
188188
# Determine if file needs transformation
189-
needs_transform = (uses_mock_response or manipulates_mock_response) and not uses_factory
190-
189+
needs_transform = (
190+
uses_mock_response or manipulates_mock_response
191+
) and not uses_factory
192+
191193
# Also flag files that use factory but still use mock_response.json.return_value in assertions
192194
if uses_factory and uses_return_value_in_assertions and not needs_transform:
193195
needs_transform = True
194-
196+
195197
if verbose or needs_transform:
196198
print(f"File: {file_path}")
197199
print(f" Has mock_response: {has_mock_response}")
@@ -200,7 +202,7 @@ def find_files_to_transform() -> List[Tuple[str, bool]]:
200202
print(f" Uses in assertions: {uses_return_value_in_assertions}")
201203
print(f" Uses factory: {uses_factory}")
202204
print(f" Needs transform: {needs_transform}")
203-
205+
204206
if needs_transform:
205207
result.append((file_path, needs_transform))
206208

tests/fitbit_client/resources/test_base.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,18 @@ def test_log_response_for_error_without_content(base_resource, mock_logger):
258258
# -----------------------------------------------------------------------------
259259

260260

261-
def test_handle_json_response(base_resource, mock_response):
261+
def test_handle_json_response(base_resource, mock_response_factory):
262262
"""Test JSON response handling"""
263-
mock_response.json.return_value = {"data": "test"}
264-
mock_response.status_code = 200
263+
expected_data = {"data": "test"}
264+
mock_response = mock_response_factory(200, expected_data)
265265

266266
result = base_resource._handle_json_response("test_method", "test/endpoint", mock_response)
267-
assert result == {"data": "test"}
267+
assert result == expected_data
268268

269269

270-
def test_handle_json_response_invalid(base_resource, mock_response):
270+
def test_handle_json_response_invalid(base_resource, mock_response_factory):
271271
"""Test invalid JSON handling"""
272+
mock_response = mock_response_factory(200)
272273
mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "doc", 0)
273274
mock_response.text = "Invalid {json"
274275

@@ -295,44 +296,49 @@ def test_handle_json_response_with_invalid_json(base_resource, mock_logger):
295296
# -----------------------------------------------------------------------------
296297

297298

298-
def test_make_request_json_success(base_resource, mock_oauth_session, mock_response):
299+
def test_make_request_json_success(base_resource, mock_oauth_session, mock_response_factory):
299300
"""Test successful JSON request"""
300-
mock_response.json.return_value = {"success": True}
301-
mock_response.headers = {"content-type": "application/json"}
302-
mock_response.status_code = 200
301+
expected_data = {"success": True}
302+
mock_response = mock_response_factory(
303+
200, expected_data, headers={"content-type": "application/json"}
304+
)
303305
mock_oauth_session.request.return_value = mock_response
304306

305307
result = base_resource._make_request("test/endpoint")
306-
assert result == {"success": True}
308+
assert result == expected_data
307309

308310

309-
def test_make_request_no_content(base_resource, mock_oauth_session, mock_response):
311+
def test_make_request_no_content(base_resource, mock_oauth_session, mock_response_factory):
310312
"""Test request with no content"""
311-
mock_response.status_code = 204
312-
mock_response.headers = {}
313-
mock_response.json.return_value = {"success": True}
313+
mock_response = mock_response_factory(204, headers={})
314314
mock_oauth_session.request.return_value = mock_response
315315

316316
result = base_resource._make_request("test/endpoint")
317317
assert result is None
318318

319319

320-
def test_make_request_xml_response(base_resource, mock_oauth_session, mock_response):
320+
def test_make_request_xml_response(base_resource, mock_oauth_session, mock_response_factory):
321321
"""Test XML response handling"""
322+
mock_response = mock_response_factory(
323+
200,
324+
headers={"content-type": "application/vnd.garmin.tcx+xml"},
325+
content_type="application/vnd.garmin.tcx+xml",
326+
)
322327
mock_response.text = "<test>data</test>"
323-
mock_response.headers = {"content-type": "application/vnd.garmin.tcx+xml"}
324-
mock_response.status_code = 200
325328
mock_oauth_session.request.return_value = mock_response
326329

327330
result = base_resource._make_request("test/endpoint")
328331
assert result == "<test>data</test>"
329332

330333

331-
def test_make_request_unexpected_content_type(base_resource, mock_oauth_session, mock_response):
334+
def test_make_request_unexpected_content_type(
335+
base_resource, mock_oauth_session, mock_response_factory
336+
):
332337
"""Test handling of unexpected content type"""
338+
mock_response = mock_response_factory(
339+
200, headers={"content-type": "text/plain"}, content_type="text/plain"
340+
)
333341
mock_response.text = "some data"
334-
mock_response.headers = {"content-type": "text/plain"}
335-
mock_response.status_code = 200
336342
mock_oauth_session.request.return_value = mock_response
337343

338344
result = base_resource._make_request("test/endpoint")

0 commit comments

Comments
 (0)