5

I'm trying to unit test my RESTful API. Here's my API:


class BaseHandler(tornado.web.RequestHandler):                    
    def __init__(self, *args, **kwargs):                          
        tornado.web.RequestHandler.__init__(self, *args, **kwargs)
        self.log = self.application.log                           
        self.db = self.application.db                             

class ProductHandler(BaseHandler):
    @tornado.web.removeslash
    def put(self, id = None, *args, **kwargs):
        try:
            self.log.info("Handling PUT request")                                                             
            if not id:                                                                                                      
                raise Exception('Object Id Required')                                                                        
            id = { '_id' : id }                                                                                                                            
            new_values = dict()                                                                                             
            name = self.get_argument('name', None)                                                                          
            description = self.get_argument('description', None)                                                            
            if name:                                                                                                        
                new_values['name'] = name                                                                                   
            if description:                                                                                                 
                new_values['description'] = description                                                                     
            self.db.products.update(id, new_values, safe = True)                                                                                                               
        except:
            self.log.error("".join(tb.format_exception(*sys.exc_info())))                                                   
            raise                                                                                                           

 class Application(tornado.web.Application):                         
     def __init__(self, config_path, test = False, *args, **kwargs): 
         handlers = [                                                
             (r"/product/?(.*)", ProductHandler),                    
         ]                                                           
         settings = dict(debug=True)                                 
         tornado.web.Application.__init__(self, handlers, **settings)
         self.log = logging.getLogger(__name__)                      
         self.config = ConfigParser()                                
         self.config.read(config_path)                               
         self.mongo_connection = Connection(                         
             host = self.config.get('mongo','host'),                 
             port = self.config.getint('mongo','port'),              
         )                                                           
         if test:                                                    
             db_name = self.config.get('test', 'mongo.db')           
         else:                                                       
             db_name = self.config.get('mongo', 'db')                
         self.log.debug("Using db:  %s" % db_name)                   
         self.db = self.mongo_connection[db_name]                    

But, here's my problem: the handler isn't seeing the name or description arguments. :(

Any suggestions?

bitcycle
  • 7,632
  • 16
  • 70
  • 121
  • 1
    I noticed that too. I ended up putting my params on the query string. I suspect it has to do with the asynchttpclient not "put"ting the write request. – lbolla Aug 17 '12 at 14:47

4 Answers4

4

As a work-around, I found them in the request.body and parsed the encoded parameters manually. It was kindof annoying, but it works.


new_values = urlparse.parse_qs(self.request.body)

# values show as lists with only one item
for k in new_values:                             
    new_values[k] = new_values[k][0]             
bitcycle
  • 7,632
  • 16
  • 70
  • 121
2

Say if you are using jQuery to send this PUT request:

$.ajax({
    type: "PUT",
    url: "/yourURL",
    data: JSON.stringify({'json':'your json here'),
    dataType: 'json'
})

The data should not be like: data: {'json': 'your json here'}, because it will automatically be encoded into query string, which needs to be parsed by parse_qs

Then in Tornado

def put(self, pid):
    d = json.loads(self.request.body)
    print d
MK Yung
  • 4,344
  • 6
  • 30
  • 35
2

put handler will parse request.body, if request had proper content-type header (application/x-www-form-urlencoded), for example if you are using tornado http client:

headers = HTTPHeaders({'content-type': 'application/x-www-form-urlencoded'})
http_client.fetch(
      HTTPRequest(url, 'PUT', body=urllib.urlencode(body), headers=headers))
0

Have you tried using a get method instead? Because depending on how you test your program, if you test it via your browser like Firefox or Chrome, they might be able to do it. Doing a HTTP PUT from a browser

If I were you I would write get instead of put. Cause then you can definitely test it in your browser.

For example, instead of:

def put ...

Try:

def get ...

Or Actually in your:

name = self.get_argument('name', None)                                                                          
description = self.get_argument('description', None) 

Why is the None there? According to the documentation:

RequestHandler.get_argument(name, default=[], strip=True)

...

If default is not provided, the argument is considered to be required, and we throw an HTTP 400 exception if it is missing.

So in your case because you are not providing a proper default, therefore your app is returning HTTP 400. Miss out the default! (i.e.)

name = self.get_argument('name')                                                                          
description = self.get_argument('description') 
Community
  • 1
  • 1
chutsu
  • 13,612
  • 19
  • 65
  • 86
  • I'm using put on purpose. That's how tornado works, from what I understand. And, I'm using get_argument(arg, None) because I'm giving the caller an option to update one or both of the fields on product. – bitcycle Aug 14 '12 at 23:22
  • You can also use `get`! That is also how tornado works. https://groups.google.com/forum/?fromgroups#!topic/python-tornado/01m13U8YghQ%5B1-25%5D, how are you testing your program? – chutsu Aug 14 '12 at 23:34
  • "I'm giving the caller an option to update one or both of the fields on product" - You can get the argument and test if the argument is `None` if None don't update that particular field. E.g. `if name is None: ... Don't update name` – chutsu Aug 14 '12 at 23:36
  • I'm using [tornado.testing.AsyncHTTPTestCase](http://www.tornadoweb.org/documentation/testing.html#tornado.testing.AsyncHTTPTestCase) for unit testing. – bitcycle Aug 15 '12 at 16:20
  • Was wondering if you could post your test code please? And does your test code work? – chutsu Aug 15 '12 at 19:37