Stack Overflow Asked by Junkrat on January 23, 2021
I am making a flask app using Flask-restx and I take inputs from the user by request parsing as follows:
from flask_restx import Resource, reqparse
from .services.calculator import DimensionCalculator
parser = reqparse.RequestParser()
parser.add_argument("dimensions", type=float,
required=True,
action='split',
help="Dimensions of the rectangle (in meters)")
parser.add_argument("angle_inclination", type=float,
required=True,
action='append',
help="Angle of inclination of the Dachfläche (Neigung)")
@ns.route("/output")
class UserOutput(Resource):
@ns.expect(parser, validation=True)
def get(self):
args = parser.parse_args()
return DimensionCalculator.inputs(**args)
where ns
is a namespace I have defined and the simplified version of DimensionCalculator.inputs
is:
class DimensionCalculator:
def inputs(**user_input):
installation_place = user_input['installation_place']
num_rectangles = user_input['num_rectangles']
dimensions = user_input['dimensions']
angle_inclination = user_input['angle_inclination']
alignment = user_input['alignment']
direction = user_input['direction']
vendor = user_input['vendor']
output = {
"installation_place": installation_place,
"num_rectangles": num_rectangles,
"area_shape": area_shape,
"vendor": vendor
}
return output
I am writing tests using pytest. I have written the tests for all the classes and methods and the only one that I am unable to test is the GET
method defined in the UserOutput
. Is there a way to test the GET
method?
Any help is appreciated.
Considering unit-testing tag, I'll present what I came up with on how you could test it in total isolation. Basically, get
method makes two function calls on dependencies, so in unit sense, you have to check if these calls have indeed been made, as well as assert the arguments, right?
Project structure for purpose of the example:
+---Project
| |
| | __init__.py
| | config.py
| | dimension_calculator.py
| | parser_impl.py
| | user_output.py
| | user_output_test.py
So, everything is flat for simplicity.
Most importantly, you have to decouple your UserOutput
module from dependencies. You shouldn't be hard-coding dependencies like that:
from .services.calculator import DimensionCalculator
Hypothetically, DimensionCalculator
could contain complex business logic which shouldn't be in scope of the test. So, here's how the UserOutput
module could look like:
from flask_restx import Resource, Api
from flask import Flask
from .config import Config
app = Flask(__name__)
api = Api(app)
ns = api.namespace('todos', description='TODO operations')
@ns.route("/output")
class UserOutput(Resource):
@ns.expect(Config.get_parser_impl(), validation=True)
def get(self):
args = Config.get_parser_impl().parse_args()
return Config.get_dimension_calculator_impl().inputs(**args)
if __name__ == '__main__':
app.run(debug=True)
As you can see, "external" dependencies can now be easily stubbed (this is part of a common pattern called dependency injection). Config module looks as follows:
from .parser_impl import parser
from .dimension_calculator import DimensionCalculator
class Config(object):
parser_impl = parser
calculator = DimensionCalculator
@staticmethod
def configure_dimension_calculator_impl(impl):
Config.calculator = impl
@staticmethod
def configure_parser_impl(impl):
Config.parser_impl = impl
@staticmethod
def get_dimension_calculator_impl():
return Config.calculator
@staticmethod
def get_parser_impl():
return Config.parser_impl
Last, but not least, is the place where we'll be stubbing the dependencies and injecting them:
from .user_output import UserOutput
from flask import Flask
from .config import Config
class ParserStub(object):
parse_args_call_count = 0
@staticmethod
def parse_args():
ParserStub.parse_args_call_count = ParserStub.parse_args_call_count + 1
return {'parser_stub': 2}
class DimensionCalculatorStub(object):
inputs_call_count = 0
@staticmethod
def inputs(**args):
DimensionCalculatorStub.inputs_call_count = DimensionCalculatorStub.inputs_call_count + 1
return {'stub': 1}
app = Flask(__name__)
def test_user_request_get():
with app.test_request_context():
# given
Config.configure_dimension_calculator_impl(DimensionCalculatorStub)
Config.configure_parser_impl(ParserStub)
uo = UserOutput()
# when
uo.get()
# then
assert DimensionCalculatorStub.inputs_call_count == 1
assert ParserStub.parse_args_call_count == 1
# assert arguments as well!
Test passes in my case. One thing missing is validation of arguments.
For completeness, I'll also include DimensionCalculator and the parser itself, though they are exactly the same as in your example. I've only modularized them:
from flask_restx import reqparse
parser = reqparse.RequestParser()
parser.add_argument("dimensions", type=float,
required=True,
action='split',
help="Dimensions of the rectangle (in meters)")
parser.add_argument("angle_inclination", type=float,
required=True,
action='append',
help="Angle of inclination of the Dachfläche (Neigung)")
and the dimension_calculator.py:
class DimensionCalculator:
@staticmethod
def inputs(**user_input):
installation_place = user_input['installation_place']
num_rectangles = user_input['num_rectangles']
dimensions = user_input['dimensions']
angle_inclination = user_input['angle_inclination']
alignment = user_input['alignment']
direction = user_input['direction']
vendor = user_input['vendor']
output = {
"installation_place": installation_place,
"num_rectangles": num_rectangles,
"area_shape": "EMPTY",
"vendor": vendor
}
return output
Important definitely there are dedicated frameworks for such stubs/mocks preparation and configuration (for example: https://pypi.org/project/pytest-mock/). I just wanted to present the concept and easiest approach possible.
Correct answer by Marek Piotrowski on January 23, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP