UnitTest with AsyncHTTPTestCase

985 views
Skip to first unread message

Kandarp Desai

unread,
Feb 15, 2011, 2:32:23 PM2/15/11
to python-tornado
Hi,

I'm new to tornado unit testing utility and have started to write simple unit test for my application. But, I'm facing some problems.
Here's my code looks like:
in TestMyApp.py:
=================
class TestMyApp(AsyncHTTPTestCase):
    """
        """
    def get_app(self):
        return my_app.Application()
    
    def test_my_app(self):
        self.http_client.fetch(self.get_url('/v1?w=1?&l=1), self.stop)
        response = self.wait()
        self.assertTrue( m_body, response.body)

if __name__ == '__main__':
    unittest.main()

in my_app.py
=================
class Application(tornado.web.Application):
     <defined my handlers>

class V1Handler(tornado.web.RequestHandler):
     def my_callback(self,response):
         <do something with response, render>

     @tornado.web.asynchronous
      def get(self):
          <read headers and cookies, do something>
         http_client = tornado.httpclient.AsyncHTTPClient()
        http_request = tornado.httpclient.HTTPRequest(<server>,
                                                      method="POST", headers=self.application.auth_header,
                                                      body=data)
        http_client.fetch(http_request,callback=self.my_callback)

But, When I ran the test, it gave me following error.

response = self.wait()
 File "../../tornado/testing.py", line 154, in timeout_func timeout)
AssertionError: Async operation timed out after 5 seconds

Can someone please guide me what I'm doing wrong ?

Regards,
Kandarp Desai     

Ben Darnell

unread,
Feb 15, 2011, 3:54:38 PM2/15/11
to python-...@googlegroups.com
The problem is that the test is using one IOLoop (a temporary object created just for one test) while the application (specifically the AsyncHTTPClient it creates) is using another (the global singleton).  

The quick and dirty solution is to override get_new_ioloop in your test case:
  def get_new_ioloop(self): 
    return IOLoop.instance()
However, this can sometimes cause odd behavior because your test cases are no longer fully isolated from each other.

The better but more complicated solution is to use the test's IOLoop in the application code.  Store the test's IOLoop or AsyncHTTPClient somewhere and use it instead of creating new AsyncHTTPClients on the global IOLoop.  Application settings are a convenient place for this kind of thing.  

-Ben

Kandarp Desai

unread,
Feb 15, 2011, 5:23:22 PM2/15/11
to python-...@googlegroups.com
Thanks Ben. I should be able to live with quick fix as I'm going to mock outside AsyncHTTPClient call in future. But, I will look into better solution u suggested.

Cheers,
Kandarp  

Krzysztof Tarnowski

unread,
May 17, 2011, 2:15:19 PM5/17/11
to python-...@googlegroups.com
The second solution works well with the application code that directly uses AsyncHTTPClient objects. However it does not work at all with tornado.auth module, as classes found there (e.g. FacebookGraphMixin) instantiate new AsyncHTTPClient objects on each request; these, b default, use global IOLoop.

Ben Darnell

unread,
May 18, 2011, 2:21:34 AM5/18/11
to python-...@googlegroups.com
True, but the auth classes also interact with external servers, which makes them poorly suited for use from unittests.  If you want to do it anyway, you can either add some sort of interface for passing in an IOLoop (awkward to do with mixins), or take the expedient way out and make the tests use the global IOLoop.  Just override get_new_ioloop() in your test subclass to return IOLoop.instance()

-Ben

Krzysztof Tarnowski

unread,
May 18, 2011, 5:58:28 AM5/18/11
to python-...@googlegroups.com
On Wed, May 18, 2011 at 8:21 AM, Ben Darnell <b...@bendarnell.com> wrote:
> True, but the auth classes also interact with external servers, which makes
> them poorly suited for use from unittests.
I agree. Better use python-mock.

> If you want to do it anyway, you
> can either add some sort of interface for passing in an IOLoop (awkward to
> do with mixins), or take the expedient way out and make the tests use the
> global IOLoop.  Just override get_new_ioloop() in your test subclass to
> return IOLoop.instance()

Thanks, it works as expected.

--
Kind Regards,
Krzysztof Tarnowski.

Reply all
Reply to author
Forward
0 new messages