Reputation: 991
I am writing some unit tests for a Google App Engine app (Python). I am using the mock library to mock out an NDB tasklet.
However, because tasklets return Future objects, I'm wondering how I can instantiate and work with my own custom NDB Future object that I can return as part of the mock's behavior. Without this ability, I'm completely at a loss for how to mock out ndb tasklets to verify that the correct methods were called within the tasklet.
In this example. async_datastore_update() is decorated with @ndb.toplevel. Inside of this function, there is an NDB tasklet that I want to mock out: yield self._async_get_specialist(specialist_name)
a_mgr = data_manager.AchievementsHandler()
# Initiate data store sync and flush async to force result
future = a_mgr.async_datastore_update()
future.get_result() # Flushes out the self.async_get_specialist() tasklet's results
# Continue testing...
Without mocking self.async_get_specialist()
, this works great by flushing the async processes in the top level function.
However, when I mock out self.async_get_specialist()
to verify behaviors, I get an exception upon calling the future's get_result()
method:
# Here, we mock out a future object, as per GAE docs
promise_obj_stub = ndb.Future()
a_mgr = data_manager.AchievementsHandler()
a_mgr._async_get_specialist = mock.Mock(return_value=promise_obj_stub)
# Initiate data store sync and flush async to force result
future = a_mgr.async_datastore_update()
future.get_result() # Throws a RuntimeError exception b/c result is never assigned to the Future object
The App Engine docs don't seem to indicate there is a way to work with Future objects other than checking for a result from a GAE api. i.e. I don't see anything to the effect of Future.set_return_value() in the docs.
Is anyone aware of the best way to a) unit test tasklets?, or b) assuming my approach makes sense, how to pass values to ndb Future objects?
Upvotes: 0
Views: 933
Reputation: 535
The accepted answer works just great, but I wanted to add the approach I use for other readers.
What I typically do is create a simple object that is a MockFuture as such:
class MockFuture(object):
def __init__(self, data=None):
self.data = data
def get_result(self):
return self.data
This allows you to set the data when you create the future which I find more natural.
future = MockFuture(data=432)
One other comment, this is useful outside of testing situations as well. Sometimes you have some conditional logic such that if you actually make the asynchronous call else you want None (or something else). In that case you can use the MockFuture (also works with the accepted answer) such that the code that deals with the result does not have to inspect the object or something like that, it just calls get_result() regardless of how things were set up.
Upvotes: 1
Reputation: 991
A ha! After playing around with the ndb Future class, it looks like Future instances have a set_result()
method that lets you pass your own information to the future (currently unlisted on the GAE Future class docs. As such, the following code worked wonderfully:
# Here, we mock out a future object, as per GAE docs
promise_obj_stub = ndb.Future()
a_mgr = data_manager.AchievementsHandler()
a_mgr._async_get_specialist = mock.Mock(return_value=promise_obj_stub)
# Initiate data store sync and flush async to force result
future = a_mgr.async_datastore_update()
promise_obj_stub.set_result(['list', 'of', 'results', 'i', 'needed']) # set a result on the Future obj
future.get_result() # Works like a charm now
# Continue testing as usual
Upvotes: 5