Skip to content

Commit cc16485

Browse files
committed
Standardize on mock_response_factory in tests
- Update test files to use mock_response_factory consistently - Replace direct manipulations of mock_response with factory usage - Keep documentation in DEVELOPMENT.md about the standardized approach
1 parent f6b183e commit cc16485

14 files changed

Lines changed: 101 additions & 77 deletions

docs/DEVELOPMENT.md

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

196196
### Response Mocking
197197

198-
The test suite uses consistent patterns for mocking API responses:
198+
The test suite uses the `mock_response_factory` fixture to create consistent,
199+
configurable mock responses:
199200

200201
```python
201-
mock_response = Mock()
202-
mock_response.json.return_value = {"data": "test"}
203-
mock_response.headers = {"content-type": "application/json"}
204-
mock_response.status_code = 200
202+
# Creating a mock response with status code and data
203+
mock_response = mock_response_factory(200, {"data": "test"})
204+
205+
# Creating a mock response with additional headers
206+
mock_response = mock_response_factory(
207+
status_code=200,
208+
json_data={"data": "test"},
209+
headers={"custom-header": "value"}
210+
)
211+
212+
# Creating a delete response with no content (204)
213+
mock_response = mock_response_factory(204)
205214
```
206215

216+
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.
219+
207220
## OAuth Callback Implementation
208221

209222
The OAuth callback mechanism is implemented using two main classes:

tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"""Tests for the get_azm_timeseries endpoint."""
44

55

6-
def test_get_azm_timeseries_with_today_date(azm_resource, mock_response):
6+
def test_get_azm_timeseries_with_today_date(azm_resource, mock_response_factory):
77
"""Test using 'today' as the date parameter"""
8-
mock_response.json.return_value = {"activities-active-zone-minutes": []}
8+
mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []})
99
azm_resource.oauth.request.return_value = mock_response
1010
result = azm_resource.get_azm_timeseries_by_date(date="today")
1111
assert result == mock_response.json.return_value

tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ def test_get_body_timeseries_by_date_period_validation(body_timeseries):
5454
)
5555

5656

57-
def test_get_body_timeseries_by_date_successful_flow(body_timeseries, mock_response):
57+
def test_get_body_timeseries_by_date_successful_flow(body_timeseries, mock_response_factory):
5858
"""Test the successful flow through validation and request for get_body_timeseries_by_date."""
5959
# Set up the mock response
60+
mock_response = mock_response_factory(200, {"expected": "response"})
6061
body_timeseries.oauth.request.return_value = mock_response
61-
mock_response.json.return_value = {"expected": "response"}
6262

6363
# Test with BMI resource type (which allows all periods)
6464
result = body_timeseries.get_body_timeseries_by_date(
@@ -103,11 +103,11 @@ def test_get_body_timeseries_by_date_successful_flow(body_timeseries, mock_respo
103103
)
104104

105105

106-
def test_get_body_timeseries_by_date_makes_correct_request(body_timeseries, mock_response):
106+
def test_get_body_timeseries_by_date_makes_correct_request(body_timeseries, mock_response_factory):
107107
"""Test that the correct endpoint is called with proper parameters."""
108108
# Set up the mock response
109+
mock_response = mock_response_factory(200, {"expected": "response"})
109110
body_timeseries.oauth.request.return_value = mock_response
110-
mock_response.json.return_value = {"expected": "response"}
111111

112112
# Call the method
113113
result = body_timeseries.get_body_timeseries_by_date(

tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ def test_get_weight_timeseries_by_date_period_validation(body_timeseries):
3636
assert f"Period {period.value} not supported for weight" in str(exc_info.value)
3737

3838

39-
def test_get_weight_timeseries_by_date_makes_correct_request(body_timeseries, mock_response):
39+
def test_get_weight_timeseries_by_date_makes_correct_request(
40+
body_timeseries, mock_response_factory
41+
):
4042
"""Test that the correct endpoint is called with proper parameters."""
4143
# Set up the mock response
44+
mock_response = mock_response_factory(200, {"expected": "response"})
4245
body_timeseries.oauth.request.return_value = mock_response
43-
mock_response.json.return_value = {"expected": "response"}
4446

4547
# Call the method
4648
result = body_timeseries.get_weight_timeseries_by_date(

tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date_range.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ def test_get_weight_timeseries_by_date_range_max_days(body_timeseries):
4242
assert "weight" in str(exc_info.value)
4343

4444

45-
def test_get_weight_timeseries_by_date_range_makes_correct_request(body_timeseries, mock_response):
45+
def test_get_weight_timeseries_by_date_range_makes_correct_request(
46+
body_timeseries, mock_response_factory
47+
):
4648
"""Test that the correct endpoint is called with proper parameters."""
4749
# Set up the mock response
50+
mock_response = mock_response_factory(200, {"expected": "response"})
4851
body_timeseries.oauth.request.return_value = mock_response
49-
mock_response.json.return_value = {"expected": "response"}
5052

5153
# Call the method with a valid date range (under 31 days)
5254
result = body_timeseries.get_weight_timeseries_by_date_range(

tests/fitbit_client/resources/friends/test_get_friends.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33
"""Tests for the get_friends endpoint."""
44

55

6-
def test_get_friends(friends_resource, mock_oauth_session, mock_response):
6+
def test_get_friends(friends_resource, mock_oauth_session, mock_response_factory):
77
"""Test getting friends list"""
8-
mock_response.json.return_value = {
9-
"data": [
10-
{
11-
"type": "person",
12-
"id": "ABC123",
13-
"attributes": {
14-
"avatar": "http://example.com/avatar.jpg",
15-
"child": False,
16-
"friend": True,
17-
"name": "Test User",
18-
},
19-
}
20-
]
21-
}
22-
mock_response.headers = {"content-type": "application/json"}
8+
mock_response = mock_response_factory(
9+
200,
10+
{
11+
"data": [
12+
{
13+
"type": "person",
14+
"id": "ABC123",
15+
"attributes": {
16+
"avatar": "http://example.com/avatar.jpg",
17+
"child": False,
18+
"friend": True,
19+
"name": "Test User",
20+
},
21+
}
22+
]
23+
},
24+
)
2325
mock_oauth_session.request.return_value = mock_response
2426
result = friends_resource.get_friends()
2527
assert len(result) == 1

tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
def test_get_heartrate_timeseries_by_date_success(
18-
heartrate_resource, mock_oauth_session, mock_response
18+
heartrate_resource, mock_oauth_session, mock_response_factory
1919
):
2020
"""Test successful retrieval of heart rate data by date and period"""
2121
response_data = {
@@ -38,7 +38,7 @@ def test_get_heartrate_timeseries_by_date_success(
3838
}
3939
]
4040
}
41-
mock_response.json.return_value = response_data
41+
mock_response = mock_response_factory(200, response_data)
4242
mock_oauth_session.request.return_value = mock_response
4343
result = heartrate_resource.get_heartrate_timeseries_by_date(
4444
date="2024-02-10", period=Period.ONE_DAY
@@ -54,9 +54,9 @@ def test_get_heartrate_timeseries_by_date_success(
5454
)
5555

5656

57-
def test_get_heartrate_timeseries_by_date_today(heartrate_resource, mock_response):
57+
def test_get_heartrate_timeseries_by_date_today(heartrate_resource, mock_response_factory):
5858
"""Test that 'today' is accepted as a valid date"""
59-
mock_response.json.return_value = {"activities-heart": []}
59+
mock_response = mock_response_factory(200, {"activities-heart": []})
6060
heartrate_resource.oauth.request.return_value = mock_response
6161
result = heartrate_resource.get_heartrate_timeseries_by_date(
6262
date="today", period=Period.ONE_DAY

tests/fitbit_client/resources/nutrition/test_create_food.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from fitbit_client.resources._constants import NutritionalValue
1212

1313

14-
def test_create_food_success(nutrition_resource, mock_response):
14+
def test_create_food_success(nutrition_resource, mock_response_factory):
1515
"""Test successful creation of a new food"""
16-
mock_response.json.return_value = {"foodId": 12345, "name": "Test Food", "calories": 100}
16+
mock_response = mock_response_factory(
17+
200, {"foodId": 12345, "name": "Test Food", "calories": 100}
18+
)
1719
nutrition_resource.oauth.request.return_value = mock_response
1820
result = nutrition_resource.create_food(
1921
name="Test Food",
@@ -47,9 +49,9 @@ def test_create_food_success(nutrition_resource, mock_response):
4749
)
4850

4951

50-
def test_create_food_with_string_nutritional_values(nutrition_resource, mock_response):
52+
def test_create_food_with_string_nutritional_values(nutrition_resource, mock_response_factory):
5153
"""Test creating food with string nutritional value keys"""
52-
mock_response.json.return_value = {"foodId": 12345, "name": "Test Food"}
54+
mock_response = mock_response_factory(200, {"foodId": 12345, "name": "Test Food"})
5355
nutrition_resource.oauth.request.return_value = mock_response
5456
result = nutrition_resource.create_food(
5557
name="Test Food",
@@ -102,9 +104,11 @@ def test_create_food_calories_from_fat_must_be_integer(nutrition_resource):
102104
assert "Calories from fat must be an integer" in str(exc_info.value)
103105

104106

105-
def test_create_food_with_calories_from_fat(nutrition_resource, mock_response):
107+
def test_create_food_with_calories_from_fat(nutrition_resource, mock_response_factory):
106108
"""Test creating food with calories from fat as an integer"""
107-
mock_response.json.return_value = {"foodId": 12345, "name": "Test Food", "calories": 100}
109+
mock_response = mock_response_factory(
110+
200, {"foodId": 12345, "name": "Test Food", "calories": 100}
111+
)
108112
nutrition_resource.oauth.request.return_value = mock_response
109113

110114
result = nutrition_resource.create_food(

tests/fitbit_client/resources/nutrition/test_create_food_log.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
from fitbit_client.resources._constants import NutritionalValue
1515

1616

17-
def test_create_food_log_with_food_id_success(nutrition_resource, mock_response):
17+
def test_create_food_log_with_food_id_success(nutrition_resource, mock_response_factory):
1818
"""Test successful creation of a food log entry using food ID"""
19-
mock_response.json.return_value = {
20-
"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}}
21-
}
19+
mock_response = mock_response_factory(
20+
200, {"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}}}
21+
)
2222
nutrition_resource.oauth.request.return_value = mock_response
2323
result = nutrition_resource.create_food_log(
2424
date="2025-02-08",
@@ -46,11 +46,11 @@ def test_create_food_log_with_food_id_success(nutrition_resource, mock_response)
4646
)
4747

4848

49-
def test_create_food_log_with_custom_food_success(nutrition_resource, mock_response):
49+
def test_create_food_log_with_custom_food_success(nutrition_resource, mock_response_factory):
5050
"""Test successful creation of a food log entry using custom food details"""
51-
mock_response.json.return_value = {
52-
"foodLog": {"logId": 12345, "loggedFood": {"name": "Custom Food", "amount": 1.0}}
53-
}
51+
mock_response = mock_response_factory(
52+
200, {"foodLog": {"logId": 12345, "loggedFood": {"name": "Custom Food", "amount": 1.0}}}
53+
)
5454
nutrition_resource.oauth.request.return_value = mock_response
5555
result = nutrition_resource.create_food_log(
5656
date="2025-02-08",
@@ -86,11 +86,11 @@ def test_create_food_log_with_custom_food_success(nutrition_resource, mock_respo
8686
)
8787

8888

89-
def test_create_food_log_with_favorite_flag(nutrition_resource, mock_response):
89+
def test_create_food_log_with_favorite_flag(nutrition_resource, mock_response_factory):
9090
"""Test that creating a food log with favorite=True sets the flag correctly"""
91-
mock_response.json.return_value = {
92-
"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}}
93-
}
91+
mock_response = mock_response_factory(
92+
200, {"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}}}
93+
)
9494
nutrition_resource.oauth.request.return_value = mock_response
9595
result = nutrition_resource.create_food_log(
9696
date="2025-02-08",
@@ -142,9 +142,9 @@ def test_create_food_log_with_favorite_flag(nutrition_resource, mock_response):
142142
)
143143

144144

145-
def test_create_food_log_with_brand_name_only(nutrition_resource, mock_response):
145+
def test_create_food_log_with_brand_name_only(nutrition_resource, mock_response_factory):
146146
"""Test creating food log with only brand name (lines 172-174)"""
147-
mock_response.json.return_value = {"foodLog": {"logId": 12345}}
147+
mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}})
148148
nutrition_resource.oauth.request.return_value = mock_response
149149
result = nutrition_resource.create_food_log(
150150
date="2025-02-08",
@@ -174,9 +174,9 @@ def test_create_food_log_with_brand_name_only(nutrition_resource, mock_response)
174174
)
175175

176176

177-
def test_create_food_log_none_handling(nutrition_resource, mock_response):
177+
def test_create_food_log_none_handling(nutrition_resource, mock_response_factory):
178178
"""Test handling of None values for food_name and calories"""
179-
mock_response.json.return_value = {"foodLog": {"logId": 12345}}
179+
mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}})
180180
nutrition_resource.oauth.request.return_value = mock_response
181181

182182
# Save original method
@@ -202,7 +202,7 @@ def test_method(date, meal_type_id, unit_id, amount, **kwargs):
202202
if calories is not None:
203203
params["calories"] = calories
204204

205-
return mock_response.json.return_value
205+
return mock_response.json()
206206

207207
try:
208208
# Replace with our test method
@@ -260,10 +260,9 @@ def test_create_food_log_invalid_date(nutrition_resource):
260260
)
261261

262262

263-
def test_create_food_log_allows_today(nutrition_resource, mock_response):
263+
def test_create_food_log_allows_today(nutrition_resource, mock_response_factory):
264264
"""Test that 'today' is accepted as a valid date"""
265-
mock_response.json.return_value = {"foodLog": {"logId": 12345}}
266-
mock_response.headers = {"content-type": "application/json"}
265+
mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}})
267266
nutrition_resource.oauth.request.return_value = mock_response
268267
nutrition_resource.create_food_log(
269268
date="today", meal_type_id=MealType.BREAKFAST, unit_id=147, amount=100.0, food_id=67890

tests/fitbit_client/resources/nutrition/test_delete_custom_food.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"""Tests for the delete_custom_food endpoint."""
44

55

6-
def test_delete_custom_food_success(nutrition_resource, mock_response):
6+
def test_delete_custom_food_success(nutrition_resource, mock_response_factory):
77
"""Test successful deletion of a custom food"""
8-
mock_response.status_code = 204
8+
mock_response = mock_response_factory(204)
99
nutrition_resource.oauth.request.return_value = mock_response
1010
result = nutrition_resource.delete_custom_food(food_id=12345)
1111
assert result is None

0 commit comments

Comments
 (0)