11import os
22
33from .mocks import AbeMock
4- from .utils import to_unicode
4+ from .utils import normalize , subkeys
55
66
77class AbeTestMixin (object ):
@@ -33,53 +33,71 @@ def get_sample_request(self, path, label):
3333 sample_request = sample .examples [label ].request
3434 return sample_request .body
3535
36- def assert_data_equal (self , data1 , data2 ):
36+ def assert_item_matches (self , real , sample ):
37+ """
38+ A primitive value matches the sample.
39+
40+ If the sample represents a parameter, then do simple pattern matching.
41+
42+ """
43+ real = normalize (real )
44+ sample = normalize (sample )
45+ self .assertEqual (real , sample )
46+
47+ def assert_data_equal (self , real , sample , non_strict = None ):
3748 """
3849 Two elements are recursively equal
50+
51+ :param non_strict:
52+ Names of fields to match non-strictly. In current implementation,
53+ only check for field presence.
3954 """
55+ non_strict = non_strict or []
4056 try :
41- if isinstance (data1 , list ):
42- self .assertIsInstance (data2 , list )
43- self .assert_data_list_equal (data1 , data2 )
44- elif isinstance (data1 , dict ):
45- self .assertIsInstance (data2 , dict )
46- self .assert_data_dict_equal (data1 , data2 )
57+ if isinstance (real , list ):
58+ self .assertIsInstance (sample , list )
59+ self .assert_data_list_equal (real , sample , non_strict )
60+ elif isinstance (real , dict ):
61+ self .assertIsInstance (sample , dict )
62+ self .assert_data_dict_equal (real , sample , non_strict )
4763 else :
48- data1 = to_unicode (data1 )
49- data2 = to_unicode (data2 )
50- self .assertIsInstance (data2 , data1 .__class__ )
51- self .assertEqual (data1 , data2 )
64+ self .assert_item_matches (real , sample )
5265 except AssertionError as exc :
53- message = str (exc ) + '\n {}\n {}\n \n ' .format (data1 , data2 )
66+ message = str (exc ) + '\n {}\n {}\n \n ' .format (real , sample )
5467 raise type (exc )(message )
5568
56- def assert_data_dict_equal (self , data1 , data2 ):
69+ def assert_data_dict_equal (self , real , sample , non_strict = None ):
5770 """
5871 Two dicts are recursively equal without taking order into account
5972 """
73+ non_strict = non_strict or []
6074 self .assertEqual (
61- len (data1 ), len (data2 ),
75+ len (real ), len (sample ),
6276 msg = 'Number of elements mismatch: {} != {}\n ' .format (
63- data1 .keys (), data2 .keys ())
77+ real .keys (), sample .keys ())
6478 )
65- for key in data1 :
66- self .assertIn (key , data2 )
67- self .assert_data_equal (data1 [key ], data2 [key ])
79+ for key in sample :
80+ self .assertIn (key , real )
81+ if key not in non_strict :
82+ inner_non_strict = subkeys (non_strict , key )
83+ self .assert_data_equal (
84+ real [key ], sample [key ], inner_non_strict )
6885
69- def assert_data_list_equal (self , data1 , data2 ):
86+ def assert_data_list_equal (self , real , sample , non_strict = None ):
7087 """
7188 Two lists are recursively equal, including ordering.
7289 """
90+ non_strict = non_strict or []
7391 self .assertEqual (
74- len (data1 ), len (data2 ),
92+ len (real ), len (sample ),
7593 msg = 'Number of elements mismatch: {} {}' .format (
76- data1 , data2 )
94+ real , sample )
7795 )
7896
7997 exceptions = []
80- for element , element2 in zip (data1 , data2 ):
98+ for real_item , sample_item in zip (real , sample ):
8199 try :
82- self .assert_data_equal (element , element2 )
100+ self .assert_data_equal (real_item , sample_item , non_strict )
83101 except AssertionError as exc :
84102 exceptions .append (exc )
85103
@@ -107,32 +125,44 @@ def assert_headers_contain(self, response_data, spec_data):
107125 "header {0}" .format (expected_header )
108126 )
109127
110- def assert_matches_request (self , sample_request , wsgi_request ):
128+ def assert_matches_request (self , sample_request , wsgi_request ,
129+ non_strict = None ):
111130 """
112131 Check that the sample request and wsgi request match.
113132 """
114- self .assertEqual (wsgi_request .META ['PATH_INFO' ], sample_request ['url' ])
115- self .assertEqual (wsgi_request .META ['REQUEST_METHOD' ],
116- sample_request ['method' ])
133+ non_strict = non_strict or []
134+
135+ if 'url' not in non_strict :
136+ self .assertEqual (wsgi_request .META ['PATH_INFO' ],
137+ sample_request ['url' ])
138+ if 'method' not in non_strict :
139+ self .assertEqual (wsgi_request .META ['REQUEST_METHOD' ],
140+ sample_request ['method' ])
117141
118- if 'headers' in sample_request :
142+ if 'headers' in sample_request and 'headers' not in non_strict :
119143 self .assert_headers_contain (
120144 wsgi_request .META , sample_request ['headers' ]
121145 )
122146
123- if 'body' in sample_request :
147+ if 'body' in sample_request and 'body' not in non_strict :
124148 self .assert_data_equal (wsgi_request .POST , sample_request ['body' ])
125149
126- def assert_matches_response (self , sample_response , wsgi_response ):
150+ def assert_matches_response (self , sample_response , wsgi_response ,
151+ non_strict = None ):
127152 """
128153 Check that the sample response and wsgi response match.
129154 """
155+ non_strict = non_strict or []
130156 self .assertEqual (wsgi_response .status_code , sample_response .status )
131157 if 'body' in sample_response :
132158 response_parsed = wsgi_response .data
133- self .assert_data_equal (response_parsed , sample_response .body )
159+ self .assert_data_equal (
160+ response_parsed , sample_response .body , non_strict )
134161
135- def assert_matches_sample (self , path , label , response ):
162+ def assert_matches_sample (
163+ self , path , label , response , non_strict_response = None ,
164+ non_strict_request = None
165+ ):
136166 """
137167 Check a URL and response against a sample.
138168
@@ -143,10 +173,19 @@ def assert_matches_sample(self, path, label, response):
143173 :param response:
144174 The actual API response we want to match with the sample.
145175 It is assumed to be a Django Rest Framework response object
176+ :param non_strict:
177+ List of fields that will not be checked for strict matching.
178+ You can use this to include server-generated fields whose exact
179+ value you don't care about in your test, like ids, dates, etc.
146180 """
181+ non_strict_response = non_strict_response or []
182+ non_strict_request = non_strict_request or []
147183 sample = self .load_sample (path )
148184 sample_request = sample .examples [label ].request
149185 sample_response = sample .examples [label ].response
150186
151- self .assert_matches_response (sample_response , response )
152- self .assert_matches_request (sample_request , response .wsgi_request )
187+ self .assert_matches_response (
188+ sample_response , response , non_strict = non_strict_response )
189+ self .assert_matches_request (
190+ sample_request , response .wsgi_request ,
191+ non_strict = non_strict_request )
0 commit comments