Skip to content

Commit 3123d27

Browse files
author
Mauko Quiroga
authored
Refactor web_api test using pytest.fixtures
Merge pull request #984 from openfisca/refactor-web_api-test-fixtures
2 parents 7566d96 + 0e42b47 commit 3123d27

13 files changed

Lines changed: 273 additions & 232 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
### 35.3.6 [#984](https://github.com/openfisca/openfisca-core/pull/984)
4+
5+
#### Technical changes
6+
7+
- In web_api tests, extract `test_client` to a fixture reusable by all the tests in the test suite.
8+
- To mitigate possible performance issues, by default the fixture is initialised once per test module.
9+
- This follows the same approach as [#997](https://github.com/openfisca/openfisca-core/pull/997)
10+
11+
312
### 35.3.5 [#997](https://github.com/openfisca/openfisca-core/pull/997)
413

514
#### Technical changes

conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pytest_plugins = [
2+
"tests.fixtures.appclient",
23
"tests.fixtures.entities",
34
"tests.fixtures.simulations",
45
"tests.fixtures.taxbenefitsystems",

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
setup(
3737
name = 'OpenFisca-Core',
38-
version = '35.3.5',
38+
version = '35.3.6',
3939
author = 'OpenFisca Team',
4040
author_email = 'contact@openfisca.org',
4141
classifiers = [

tests/fixtures/appclient.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import pytest
2+
3+
from openfisca_web_api import app
4+
5+
6+
@pytest.fixture(scope="module")
7+
def test_client(tax_benefit_system):
8+
""" This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system`
9+
fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example:
10+
11+
```
12+
from openfisca_country_template import entities
13+
from openfisca_core import periods
14+
from openfisca_core.variables import Variable
15+
...
16+
17+
class new_variable(Variable):
18+
value_type = float
19+
entity = entities.Person
20+
definition_period = periods.MONTH
21+
label = "New variable"
22+
reference = "https://law.gov.example/new_variable" # Always use the most official source
23+
24+
tax_benefit_system.add_variable(new_variable)
25+
flask_app = app.create_app(tax_benefit_system)
26+
```
27+
"""
28+
29+
# Create the test API client
30+
flask_app = app.create_app(tax_benefit_system)
31+
return flask_app.test_client()

tests/web_api/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
# -*- coding: utf-8 -*-
2-
31
import pkg_resources
4-
from openfisca_web_api.app import create_app
5-
from openfisca_core.scripts import build_tax_benefit_system
62

73
TEST_COUNTRY_PACKAGE_NAME = 'openfisca_country_template'
84
distribution = pkg_resources.get_distribution(TEST_COUNTRY_PACKAGE_NAME)
9-
tax_benefit_system = build_tax_benefit_system(TEST_COUNTRY_PACKAGE_NAME, extensions = None, reforms = None)
10-
subject = create_app(tax_benefit_system).test_client()

tests/web_api/test_calculate.py

Lines changed: 61 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
1-
# -*- coding: utf-8 -*-
2-
3-
import os
1+
import copy
2+
import dpath
43
import json
5-
from http.client import OK, BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR
6-
from copy import deepcopy
7-
4+
from http import client
5+
import os
86
import pytest
9-
import dpath
107

118
from openfisca_country_template.situation_examples import couple
129

13-
from . import subject
14-
1510

16-
def post_json(data = None, file = None):
11+
def post_json(client, data = None, file = None):
1712
if file:
1813
file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'assets', file)
1914
with open(file_path, 'r') as file:
2015
data = file.read()
21-
return subject.post('/calculate', data = data, content_type = 'application/json')
16+
return client.post('/calculate', data = data, content_type = 'application/json')
2217

2318

24-
def check_response(data, expected_error_code, path_to_check, content_to_check):
25-
response = post_json(data)
19+
def check_response(client, data, expected_error_code, path_to_check, content_to_check):
20+
response = post_json(client, data)
2621
assert response.status_code == expected_error_code
2722
json_response = json.loads(response.data.decode('utf-8'))
2823
if path_to_check:
@@ -31,32 +26,32 @@ def check_response(data, expected_error_code, path_to_check, content_to_check):
3126

3227

3328
@pytest.mark.parametrize("test", [
34-
('{"a" : "x", "b"}', BAD_REQUEST, 'error', 'Invalid JSON'),
35-
('["An", "array"]', BAD_REQUEST, 'error', 'Invalid type'),
36-
('{"persons": {}}', BAD_REQUEST, 'persons', 'At least one person'),
37-
('{"persons": {"bob": {}}, "unknown_entity": {}}', BAD_REQUEST, 'unknown_entity', 'entities are not found',),
38-
('{"persons": {"bob": {}}, "households": {"dupont": {"parents": {}}}}', BAD_REQUEST, 'households/dupont/parents', 'type',),
39-
('{"persons": {"bob": {"unknown_variable": {}}}}', NOT_FOUND, 'persons/bob/unknown_variable', 'You tried to calculate or to set',),
40-
('{"persons": {"bob": {"housing_allowance": {}}}}', BAD_REQUEST, 'persons/bob/housing_allowance', "You tried to compute the variable 'housing_allowance' for the entity 'persons'",),
41-
('{"persons": {"bob": {"salary": 4000 }}}', BAD_REQUEST, 'persons/bob/salary', 'period',),
42-
('{"persons": {"bob": {"salary": {"2017-01": "toto"} }}}', BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',),
43-
('{"persons": {"bob": {"salary": {"2017-01": {}} }}}', BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',),
44-
('{"persons": {"bob": {"age": {"2017-01": "toto"} }}}', BAD_REQUEST, 'persons/bob/age/2017-01', 'expected type integer',),
45-
('{"persons": {"bob": {"birth": {"2017-01": "toto"} }}}', BAD_REQUEST, 'persons/bob/birth/2017-01', 'Can\'t deal with date',),
46-
('{"persons": {"bob": {}}, "households": {"household": {"parents": ["unexpected_person_id"]}}}', BAD_REQUEST, 'households/household/parents', 'has not been declared in persons',),
47-
('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", "bob"]}}}', BAD_REQUEST, 'households/household/parents', 'has been declared more than once',),
48-
('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", {}]}}}', BAD_REQUEST, 'households/household/parents/1', 'Invalid type',),
49-
('{"persons": {"bob": {"salary": {"invalid period": 2000 }}}}', BAD_REQUEST, 'persons/bob/salary', 'Expected a period',),
50-
('{"persons": {"bob": {"salary": {"invalid period": null }}}}', BAD_REQUEST, 'persons/bob/salary', 'Expected a period',),
51-
('{"persons": {"bob": {"basic_income": {"2017": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', BAD_REQUEST, 'persons/bob/basic_income/2017', '"basic_income" can only be set for one month',),
52-
('{"persons": {"bob": {"salary": {"ETERNITY": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', BAD_REQUEST, 'persons/bob/salary/ETERNITY', 'salary is only defined for months',),
53-
('{"persons": {"alice": {}, "bob": {}, "charlie": {}}, "households": {"_": {"parents": ["alice", "bob", "charlie"]}}}', BAD_REQUEST, 'households/_/parents', 'at most 2 parents in a household',),
29+
('{"a" : "x", "b"}', client.BAD_REQUEST, 'error', 'Invalid JSON'),
30+
('["An", "array"]', client.BAD_REQUEST, 'error', 'Invalid type'),
31+
('{"persons": {}}', client.BAD_REQUEST, 'persons', 'At least one person'),
32+
('{"persons": {"bob": {}}, "unknown_entity": {}}', client.BAD_REQUEST, 'unknown_entity', 'entities are not found',),
33+
('{"persons": {"bob": {}}, "households": {"dupont": {"parents": {}}}}', client.BAD_REQUEST, 'households/dupont/parents', 'type',),
34+
('{"persons": {"bob": {"unknown_variable": {}}}}', client.NOT_FOUND, 'persons/bob/unknown_variable', 'You tried to calculate or to set',),
35+
('{"persons": {"bob": {"housing_allowance": {}}}}', client.BAD_REQUEST, 'persons/bob/housing_allowance', "You tried to compute the variable 'housing_allowance' for the entity 'persons'",),
36+
('{"persons": {"bob": {"salary": 4000 }}}', client.BAD_REQUEST, 'persons/bob/salary', 'period',),
37+
('{"persons": {"bob": {"salary": {"2017-01": "toto"} }}}', client.BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',),
38+
('{"persons": {"bob": {"salary": {"2017-01": {}} }}}', client.BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',),
39+
('{"persons": {"bob": {"age": {"2017-01": "toto"} }}}', client.BAD_REQUEST, 'persons/bob/age/2017-01', 'expected type integer',),
40+
('{"persons": {"bob": {"birth": {"2017-01": "toto"} }}}', client.BAD_REQUEST, 'persons/bob/birth/2017-01', 'Can\'t deal with date',),
41+
('{"persons": {"bob": {}}, "households": {"household": {"parents": ["unexpected_person_id"]}}}', client.BAD_REQUEST, 'households/household/parents', 'has not been declared in persons',),
42+
('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", "bob"]}}}', client.BAD_REQUEST, 'households/household/parents', 'has been declared more than once',),
43+
('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", {}]}}}', client.BAD_REQUEST, 'households/household/parents/1', 'Invalid type',),
44+
('{"persons": {"bob": {"salary": {"invalid period": 2000 }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'Expected a period',),
45+
('{"persons": {"bob": {"salary": {"invalid period": null }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'Expected a period',),
46+
('{"persons": {"bob": {"basic_income": {"2017": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', client.BAD_REQUEST, 'persons/bob/basic_income/2017', '"basic_income" can only be set for one month',),
47+
('{"persons": {"bob": {"salary": {"ETERNITY": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', client.BAD_REQUEST, 'persons/bob/salary/ETERNITY', 'salary is only defined for months',),
48+
('{"persons": {"alice": {}, "bob": {}, "charlie": {}}, "households": {"_": {"parents": ["alice", "bob", "charlie"]}}}', client.BAD_REQUEST, 'households/_/parents', 'at most 2 parents in a household',),
5449
])
55-
def test_responses(test):
56-
check_response(*test)
50+
def test_responses(test_client, test):
51+
check_response(test_client, *test)
5752

5853

59-
def test_basic_calculation():
54+
def test_basic_calculation(test_client):
6055
simulation_json = json.dumps({
6156
"persons": {
6257
"bill": {
@@ -101,8 +96,8 @@ def test_basic_calculation():
10196
}
10297
})
10398

104-
response = post_json(simulation_json)
105-
assert response.status_code == OK
99+
response = post_json(test_client, simulation_json)
100+
assert response.status_code == client.OK
106101
response_json = json.loads(response.data.decode('utf-8'))
107102
assert dpath.get(response_json, 'persons/bill/basic_income/2017-12') == 600 # Universal basic income
108103
assert dpath.get(response_json, 'persons/bill/income_tax/2017-12') == 300 # 15% of the salary
@@ -112,7 +107,7 @@ def test_basic_calculation():
112107
assert dpath.get(response_json, 'households/first_household/housing_tax/2017') == 3000
113108

114109

115-
def test_enums_sending_identifier():
110+
def test_enums_sending_identifier(test_client):
116111
simulation_json = json.dumps({
117112
"persons": {
118113
"bill": {}
@@ -133,13 +128,13 @@ def test_enums_sending_identifier():
133128
}
134129
})
135130

136-
response = post_json(simulation_json)
137-
assert response.status_code == OK
131+
response = post_json(test_client, simulation_json)
132+
assert response.status_code == client.OK
138133
response_json = json.loads(response.data.decode('utf-8'))
139134
assert dpath.get(response_json, 'households/_/housing_tax/2017') == 0
140135

141136

142-
def test_enum_output():
137+
def test_enum_output(test_client):
143138
simulation_json = json.dumps({
144139
"persons": {
145140
"bill": {},
@@ -154,13 +149,13 @@ def test_enum_output():
154149
}
155150
})
156151

157-
response = post_json(simulation_json)
158-
assert response.status_code == OK
152+
response = post_json(test_client, simulation_json)
153+
assert response.status_code == client.OK
159154
response_json = json.loads(response.data.decode('utf-8'))
160155
assert dpath.get(response_json, "households/_/housing_occupancy_status/2017-01") == "tenant"
161156

162157

163-
def test_enum_wrong_value():
158+
def test_enum_wrong_value(test_client):
164159
simulation_json = json.dumps({
165160
"persons": {
166161
"bill": {},
@@ -175,15 +170,15 @@ def test_enum_wrong_value():
175170
}
176171
})
177172

178-
response = post_json(simulation_json)
179-
assert response.status_code == BAD_REQUEST
173+
response = post_json(test_client, simulation_json)
174+
assert response.status_code == client.BAD_REQUEST
180175
response_json = json.loads(response.data.decode('utf-8'))
181176
message = "Possible values are ['owner', 'tenant', 'free_lodger', 'homeless']"
182177
text = dpath.get(response_json, "households/_/housing_occupancy_status/2017-01")
183178
assert message in text
184179

185180

186-
def test_encoding_variable_value():
181+
def test_encoding_variable_value(test_client):
187182
simulation_json = json.dumps({
188183
"persons": {
189184
"toto": {}
@@ -202,15 +197,15 @@ def test_encoding_variable_value():
202197
})
203198

204199
# No UnicodeDecodeError
205-
response = post_json(simulation_json)
206-
assert response.status_code == BAD_REQUEST, response.data.decode('utf-8')
200+
response = post_json(test_client, simulation_json)
201+
assert response.status_code == client.BAD_REQUEST, response.data.decode('utf-8')
207202
response_json = json.loads(response.data.decode('utf-8'))
208203
message = "'Locataire ou sous-locataire d‘un logement loué vide non-HLM' is not a known value for 'housing_occupancy_status'. Possible values are "
209204
text = dpath.get(response_json, 'households/_/housing_occupancy_status/2017-07')
210205
assert message in text
211206

212207

213-
def test_encoding_entity_name():
208+
def test_encoding_entity_name(test_client):
214209
simulation_json = json.dumps({
215210
"persons": {
216211
"O‘Ryan": {},
@@ -227,17 +222,17 @@ def test_encoding_entity_name():
227222
})
228223

229224
# No UnicodeDecodeError
230-
response = post_json(simulation_json)
225+
response = post_json(test_client, simulation_json)
231226
response_json = json.loads(response.data.decode('utf-8'))
232227

233228
# In Python 3, there is no encoding issue.
234-
if response.status_code != OK:
229+
if response.status_code != client.OK:
235230
message = "'O‘Ryan' is not a valid ASCII value."
236231
text = response_json['error']
237232
assert message in text
238233

239234

240-
def test_encoding_period_id():
235+
def test_encoding_period_id(test_client):
241236
simulation_json = json.dumps({
242237
"persons": {
243238
"bill": {
@@ -268,8 +263,8 @@ def test_encoding_period_id():
268263
})
269264

270265
# No UnicodeDecodeError
271-
response = post_json(simulation_json)
272-
assert response.status_code == BAD_REQUEST
266+
response = post_json(test_client, simulation_json)
267+
assert response.status_code == client.BAD_REQUEST
273268
response_json = json.loads(response.data.decode('utf-8'))
274269

275270
# In Python 3, there is no encoding issue.
@@ -279,17 +274,17 @@ def test_encoding_period_id():
279274
assert message in text
280275

281276

282-
def test_str_variable():
283-
new_couple = deepcopy(couple)
277+
def test_str_variable(test_client):
278+
new_couple = copy.deepcopy(couple)
284279
new_couple['households']['_']['postal_code'] = {'2017-01': None}
285280
simulation_json = json.dumps(new_couple)
286281

287-
response = subject.post('/calculate', data = simulation_json, content_type = 'application/json')
282+
response = test_client.post('/calculate', data = simulation_json, content_type = 'application/json')
288283

289-
assert response.status_code == OK
284+
assert response.status_code == client.OK
290285

291286

292-
def test_periods():
287+
def test_periods(test_client):
293288
simulation_json = json.dumps({
294289
"persons": {
295290
"bill": {}
@@ -307,8 +302,8 @@ def test_periods():
307302
}
308303
})
309304

310-
response = post_json(simulation_json)
311-
assert response.status_code == OK
305+
response = post_json(test_client, simulation_json)
306+
assert response.status_code == client.OK
312307

313308
response_json = json.loads(response.data.decode('utf-8'))
314309

@@ -319,7 +314,7 @@ def test_periods():
319314
assert monthly_variable == {'2017-01': 'tenant'}
320315

321316

322-
def test_gracefully_handle_unexpected_errors():
317+
def test_gracefully_handle_unexpected_errors(test_client):
323318
"""
324319
Context
325320
========
@@ -358,8 +353,8 @@ def test_gracefully_handle_unexpected_errors():
358353
}
359354
})
360355

361-
response = post_json(simulation_json)
362-
assert response.status_code == INTERNAL_SERVER_ERROR
356+
response = post_json(test_client, simulation_json)
357+
assert response.status_code == client.INTERNAL_SERVER_ERROR
363358

364359
error = json.loads(response.data)["error"]
365360
assert f"Unable to compute variable '{variable}' for period {period}" in error

tests/web_api/test_entities.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
# -*- coding: utf-8 -*-
22

3-
from http.client import OK
3+
from http import client
44
import json
5-
import openfisca_country_template
6-
from . import subject
75

8-
entities_response = subject.get('/entities')
6+
from openfisca_country_template import entities
7+
98

109
# /entities
1110

1211

13-
def test_return_code():
14-
assert entities_response.status_code == OK
12+
def test_return_code(test_client):
13+
entities_response = test_client.get('/entities')
14+
assert entities_response.status_code == client.OK
1515

1616

17-
def test_response_data():
18-
entities = json.loads(entities_response.data.decode('utf-8'))
19-
test_documentation = openfisca_country_template.entities.Household.doc.strip()
17+
def test_response_data(test_client):
18+
entities_response = test_client.get('/entities')
19+
entities_dict = json.loads(entities_response.data.decode('utf-8'))
20+
test_documentation = entities.Household.doc.strip()
2021

21-
assert entities['household'] == {
22+
assert entities_dict['household'] == {
2223
'description': 'All the people in a family or group who live together in the same place.',
2324
'documentation': test_documentation,
2425
'plural': 'households',

tests/web_api/test_headers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# -*- coding: utf-8 -*-
22

3-
from . import distribution, subject
3+
from . import distribution
44

5-
parameters_response = subject.get('/parameters')
65

7-
8-
def test_package_name_header():
6+
def test_package_name_header(test_client):
7+
parameters_response = test_client.get('/parameters')
98
assert parameters_response.headers.get('Country-Package') == distribution.key
109

1110

12-
def test_package_version_header():
11+
def test_package_version_header(test_client):
12+
parameters_response = test_client.get('/parameters')
1313
assert parameters_response.headers.get('Country-Package-Version') == distribution.version

0 commit comments

Comments
 (0)