Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3""" 

4 A py.test plugin which helps testing Flask applications. 

5 

6 :copyright: (c) by Vital Kudzelka 

7 :license: MIT 

8""" 

9import sys 

10 

11import pytest 

12 

13from flask import json 

14from werkzeug.utils import cached_property 

15 

16from .fixtures import ( 

17 client, config, accept_json, accept_jsonp, accept_any, accept_mimetype, 

18 client_class, live_server, request_ctx 

19) 

20from .pytest_compat import getfixturevalue 

21 

22 

23class JSONResponse(object): 

24 """Mixin with testing helper methods for JSON responses.""" 

25 

26 @cached_property 

27 def json(self): 

28 """Try to deserialize response data (a string containing a valid JSON 

29 document) to a Python object by passing it to the underlying 

30 :mod:`flask.json` module. 

31 """ 

32 return json.loads(self.data) 

33 

34 def __eq__(self, other): 

35 if isinstance(other, int): 

36 return self.status_code == other 

37 # even though the Python 2-specific code works on Python 3, keep the two versions 

38 # separate so we can simplify the code once Python 2 support is dropped 

39 if sys.version_info[0] == 2: 

40 try: 

41 super_eq = super(JSONResponse, self).__eq__ 

42 except AttributeError: 

43 return NotImplemented 

44 else: 

45 return super_eq(other) 

46 else: 

47 return super(JSONResponse, self).__eq__(other) 

48 

49 def __ne__(self, other): 

50 return not self == other 

51 

52 

53def pytest_assertrepr_compare(op, left, right): 

54 if isinstance(left, JSONResponse) and op == '==' and isinstance(right, int): 

55 return [ 

56 'Mismatch in status code for response: {} != {}'.format( 

57 left.status_code, 

58 right, 

59 ), 

60 'Response status: {}'.format(left.status), 

61 ] 

62 return None 

63 

64 

65def _make_test_response_class(response_class): 

66 """Extends the response class with special attribute to test JSON 

67 responses. Don't override user-defined `json` attribute if any. 

68 

69 :param response_class: An original response class. 

70 """ 

71 if 'json' in response_class.__dict__: 

72 return response_class 

73 

74 return type(str(JSONResponse), (response_class, JSONResponse), {}) 

75 

76 

77@pytest.fixture(autouse=True) 

78def _monkeypatch_response_class(request, monkeypatch): 

79 """Set custom response class before test suite and restore the original 

80 after. Custom response has `json` property to easily test JSON responses:: 

81 

82 @app.route('/ping') 

83 def ping(): 

84 return jsonify(ping='pong') 

85 

86 def test_json(client): 

87 res = client.get(url_for('ping')) 

88 assert res.json == {'ping': 'pong'} 

89 

90 """ 

91 if 'app' not in request.fixturenames: 

92 return 

93 

94 app = getfixturevalue(request, 'app') 

95 monkeypatch.setattr(app, 'response_class', 

96 _make_test_response_class(app.response_class)) 

97 

98 

99@pytest.fixture(autouse=True) 

100def _push_request_context(request): 

101 """During tests execution request context has been pushed, e.g. `url_for`, 

102 `session`, etc. can be used in tests as is:: 

103 

104 def test_app(app, client): 

105 assert client.get(url_for('myview')).status_code == 200 

106 

107 """ 

108 if 'app' not in request.fixturenames: 

109 return 

110 

111 app = getfixturevalue(request, 'app') 

112 

113 # Get application bound to the live server if ``live_server`` fixture 

114 # is applied. Live server application has an explicit ``SERVER_NAME``, 

115 # so ``url_for`` function generates a complete URL for endpoint which 

116 # includes application port as well. 

117 if 'live_server' in request.fixturenames: 

118 app = getfixturevalue(request, 'live_server').app 

119 

120 ctx = app.test_request_context() 

121 ctx.push() 

122 

123 def teardown(): 

124 ctx.pop() 

125 

126 request.addfinalizer(teardown) 

127 

128 

129@pytest.fixture(autouse=True) 

130def _configure_application(request, monkeypatch): 

131 """Use `pytest.mark.options` decorator to pass options to your application 

132 factory:: 

133 

134 @pytest.mark.options(debug=False) 

135 def test_something(app): 

136 assert not app.debug, 'the application works not in debug mode!' 

137 

138 """ 

139 if 'app' not in request.fixturenames: 

140 return 

141 

142 app = getfixturevalue(request, 'app') 

143 for options in request.node.iter_markers('options'): 

144 for key, value in options.kwargs.items(): 

145 monkeypatch.setitem(app.config, key.upper(), value) 

146 

147 

148def pytest_addoption(parser): 

149 group = parser.getgroup('flask') 

150 group.addoption('--start-live-server', 

151 action="store_true", dest="start_live_server", default=True, 

152 help="start server automatically when live_server " 

153 "fixture is applied (enabled by default).") 

154 group.addoption('--no-start-live-server', 

155 action="store_false", dest="start_live_server", 

156 help="don't start server automatically when live_server " 

157 "fixture is applied.") 

158 group.addoption('--live-server-clean-stop', 

159 action="store_true", dest="live_server_clean_stop", default=True, 

160 help="attempt to kill the live server cleanly.") 

161 group.addoption('--no-live-server-clean-stop', 

162 action="store_false", dest="live_server_clean_stop", 

163 help="terminate the server forcefully after stop.") 

164 group.addoption('--live-server-host', action='store', default='localhost', type=str, 

165 help='use a host where to listen (default localhost).') 

166 group.addoption('--live-server-port', action='store', default=0, type=int, 

167 help='use a fixed port for the live_server fixture.') 

168 

169 

170def pytest_configure(config): 

171 config.addinivalue_line( 

172 'markers', 

173 'app(options): pass options to your application factory') 

174 config.addinivalue_line('markers', 'options: app config manipulation')