I am creating a test module for a Flask application. In this Resource
's get() method, I fetch data from Mongo and then iterate through it to produce output. I am mocking the collection's find()
method to return my iterable. The problem is that inside get()
where I loop through the iterable, it skips as though it was empty. So I tried to loop through the iterable inside the test instead and I can successfully see the 3 dicts that it should contain.
Class properties:
class _TestAll(BaseAllReports):
collection = MagicMock()
bool_columns = ('bool1', 'bool2')
string_set_columns = ('string1', 'string2')
int_columns = ('int1', 'int2')
text_columns = ('text1', 'text2')
stats_columns = ('bool1', 'int1')
Resource.get():
def get(self):
args = self.parser().parse_args()
search = self.search_doc(args)
docs = self.collection.find(search, {'_id': False})
print(docs)
ids, total_hurt, total_dead = set(), 0, 0
stats = dict((x, {}) for x in self.stats_columns)
stats['month'] = {}
for d in docs:
print('get', d)
if d['id'] in ids:
continue
else:
ids.add(d['id'])
for s in self.stats_columns:
if s in self.bool_columns:
key = u'Sí' if d[s] else 'No'
else:
key = d[s]
number = stats[s].get(key, 0) + 1
stats[s][key] = number
month_key = d['timestamp'].strftime('%b')
month_summary = stats['month'].get(month_key, {'hurt': 0, 'dead': 0})
month_summary['hurt'] += d['total_hurt']
month_summary['dead'] += d['total_dead']
stats['month'][month_key] = month_summary
total_hurt += d['total_hurt']
total_dead += d['total_dead']
return {
'incidents': len(ids),
'involved': docs.count(),
'affected': total_hurt + total_dead,
'hurt': total_hurt,
'dead': total_dead,
'stats': stats
}
Test setup:
@classmethod
def setUpClass(cls):
app.testing = True
cls.app = app.test_client()
cls.url = '/incidents'
cls.url_with_key = '/incidents?key=testKeyHash'
api.add_resource(_TestAll, cls.url)
Test:
def test_get(self):
with patch('__main__._TestAll.collection.find') as search:
answer = []
for i in range(3):
answer.append({
'id': i,
'bool1': True, 'bool2': False,
'string1': 'test', 'string2': 'test',
'int1': 1, 'int2': 2,
'text1': 'test', 'text2': 'test',
'timestamp': datetime.now(), 'total_hurt': 1, 'total_dead': 0})
search.__iter__.return_value = answer
search.return_value.count.return_value = len(answer)
response = self.app.get(self.url_with_key)
data = json.loads(response.data.decode())
for i in search:
print('test', i)
print(data)
self.assertEqual(_TestAll.collection.find.call_count, 1)
self.assertIn('stats', data)
for s in _TestAll.stats_columns:
self.assertIn(s, data['stats'])
Terminal output:
<MagicMock name='find()' id='4423760080'>
('test', {'timestamp': datetime.datetime(2017, 5, 25, 13, 3, 9, 255912), 'text2': 'test', 'text1': 'test', 'int1': 1, 'int2': 2, 'id': 0, 'bool1': True, 'bool2': False, 'total_hurt': 1, 'total_dead': 0, 'string2': 'test', 'string1': 'test'})
('test', {'timestamp': datetime.datetime(2017, 5, 25, 13, 3, 9, 255923), 'text2': 'test', 'text1': 'test', 'int1': 1, 'int2': 2, 'id': 1, 'bool1': True, 'bool2': False, 'total_hurt': 1, 'total_dead': 0, 'string2': 'test', 'string1': 'test'})
('test', {'timestamp': datetime.datetime(2017, 5, 25, 13, 3, 9, 255928), 'text2': 'test', 'text1': 'test', 'int1': 1, 'int2': 2, 'id': 2, 'bool1': True, 'bool2': False, 'total_hurt': 1, 'total_dead': 0, 'string2': 'test', 'string1': 'test'})
{u'stats': {u'bool1': {}, u'int1': {}, u'month': {}}, u'involved': 3, u'dead': 0, u'hurt': 0, u'incidents': 0, u'affected': 0}
I don't understand why the Resource doesn't loop properly through the iterable but the test can't. Any help is appreciated.
Thanks
When setting the __iter__
value, the line was
search.__iter__.return_value = answer
I didn't take into account the fact that filter()
was callable. The right way to achieve what I was trying is:
search.return_value.__iter__.return_value = answer
Because the search
Mock was being called, a new MagicMock
was returned, which obviously didn't have the __iter__
property set. The reource's get()
and the test function were accessing different mocks that's why it only worked on one of them.
The way I found out was by printing the mock inside the test method as well and getting a different mock id for it.