how to do unit test on tornado - python

The tornado testing subject doc is so simple, I am not quite sure how to do a unit test on tornado. like that:
here is a api.py:
import tornado
import logging
from tornado.web import RequestHandler
import time
class AnalyticsBWSpecificHour(RequestHandler):
def get(self):
return self.write({'message':'no get method'})
class Application(tornado.web.Application):
def __init__(self,**kwargs):
api_handlers = [
(r"/", AnalyticsBWSpecificHour),
]
logging.debug(api_handlers)
super(Application, self).__init__(api_handlers, **kwargs)
and the test_tornado.py :
from api import Application
from tornado.testing import AsyncHTTPTestCase
import tornado
import logging
logging.basicConfig(level=logging.DEBUG)
import unittest
class ApiTestCase(AsyncHTTPTestCase):
def get_app(self):
self.app = Application(debug=True)
return self.app
def test_status(self):
print(self.get_url('/'))
response = self.fetch(self.get_url('/'),method='GET')
self.assertEqual(response.code,200)
if __name__ == '__main__':
unittest.main()
even this is quite simple example, I also get the 599 error. please help me.

response = self.fetch(self.get_url('/'),method='GET')
self.fetch() calls self.get_url for you. Either do self.fetch('/') or self.http_client.fetch(self.get_url('/')), but don't mix the two.
Also don't pass debug=True in tests; the autoreload will do the wrong thing in a unittest environment.

Related

Mocking a load time variable in Python

I am trying to get coverage up on my Python code and am testing my Flask app. Without being too specific, let's look at the sample code here:
# from /my_project/app_demo/app.py
from private_module import logs
from flask import Flask
import logging
logs.init_logging()
logger = logging.getLogger(__name__)
app = Flask(__name__)
#app.route("/greet/<name:name>", methods=["GET"])
def greet(name):
logger.info(f"{name} wants a greeting")
return f"Hello, {name}!"
if __name__ == "__main__":
app.run(debug=True)
If I am to write a unit test for the greet function, I want to mock the logger to assert it is called when it is created and when the info method is called. For this example, the sources root folder is app_demo and there is an init.py in that python folder.
# from /my_project/tests/test_app.py
import pytest
from unittest import mock
#pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_greet(client):
logs_mock = mock.patch("app_demo.app.logs.init_logging")
logger_mock = mock.patch("app_demo.app.logging.getLogger")
actual = client.get("George")
assert "Hello, George!" == actual
# these next assertions do not work
logs_mock.assert_called_once_with()
logging_mock.assert_called_once_with("app_demo.app") # not sure if this will be "__main__"
logging_mock.return_value.info.assert_called_once_with("George wants a greeting")
If I debug where the logger.info is called in the greet function, the object is a logger and not the mock object I want it to be.
I have tried making the mock in another fixture as well but that did not make it work either.

Re-running an initialization function for each api reload

I am currently running an api project (fastapi served with uvicorn) where at startup a series of initializations are done.
If I set reload=True as an argument to my uvicorn startup function, my api correctly recognizes code changes and reloads. The issue is that I don't get the initializations when reloading the api, and this ends up breaking my workflow and effectively blocking me from using what I consider a very useful feature.
Example:
# fastapi app object is located in another module
def main() -> None:
various_initializations()
if __name__ == "__main__":
main()
uvicorn.run("my.project.location.for:app", host="0.0.0.0", port=my_port, reload=True)
In this case I need my main function to be run at each reload.
Edit:
Testable example
main.py
import uvicorn
from my_class import MyClass
def init_stuff() -> None:
MyClass.initialize()
if __name__ == "__main__":
init_stuff()
uvicorn.run("api:app", host="0.0.0.0", port=10000, reload=True)
my_class.py
class MyClass:
initialized = False
#staticmethod
def initialize() -> None:
MyClass.initialized = True
#staticmethod
def do_stuff() -> None:
if not MyClass.initialized:
raise ValueError("not initialized!")
print("doing stuff")
api.py
from fastapi import FastAPI
from my_class import MyClass
app = FastAPI()
#app.get("/stuff")
def api_stuff() -> None:
return MyClass.do_stuff()
When reload=False, if I hit the /stuff endpoint I get a correct behavior (doing stuff is printed on the terminal). With reload=True I get an exception indicating that MyClass hasn't been initialized (ValueError: not initialized!)
as #MatsLindh suggested, the (a?) way to do it is to hook on the startup event of fastapi, making the initialization run on each reload.
my main.py file now looks like this:
import uvicorn
from my_class import MyClass
from api import app
#app.on_event("startup")
def init_stuff() -> None:
MyClass.initialize()
if __name__ == "__main__":
uvicorn.run("api:app", host="0.0.0.0", port=10000, reload=True)
Happy api noises

Python Mock Test

I am trying to mock test an endpoint that gets the time and date.
I have viewed several tutorials and python docs, but I am still getting stumped by the mock test.
Any help is appreciated greatly
from flask import Flask, redirect, url_for
import json
import urllib.request
import requests
app = Flask(__name__)
#app.route('/')
def welcome():
return "Hello"
#app.route('/<zone>')
def Endpoint(zone):
address = f"http://worldclockapi.com/api/json/{zone}/now"
response = urllib.request.urlopen(address)
result = json.loads(response.read())
time = result['currentDateTime']
return time
if __name__ == "__main__":
app.run(debug=True)
My attempt.
I think I am still calling the external element.
I want to use a fake JSON string and actually mock with that.
The first test passes when I run it. But I don't think it is a true mock.
#!/usr/bin/python
import unittest
from unittest import TestCase
from unittest.mock import patch, Mock
#name of endpoint program
import question
class TestingMock(TestCase):
#patch('question.Endpoint')
def test_call(self, MockTime):
current = MockTime()
current.posts.return_value = [
{"$id": "1", "currentDateTime": "2020-07-17T12:31-04:00", "utcOffset": "-04:00:00"}
]
response = current.posts()
self.assertIsNotNone(response)
self.assertIsInstance(response[0], dict)
#patch('question.Endpoint')
def test_response(mock_get):
mock_get.return_value.ok = True
respond = question.Endpoint()
assert_is_not_none(respond)
if __name__ == '__main__':
unittest.main()
You are conflicting with your root URL handler. Try changing #app.route('/<zone>') to #app.route('/time/<zone>'), then navigate to that url

How can I use mock for testing inside greenlet?

I use bottle & gevent for my python (2.7.6) application.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from gevent import spawn, monkey
from bottle import Bottle
from .settings import MONGODB_HOST, MONGODB_PORT, MONGODB_NAME
monkey.patch_all()
mongo_client = MongoClient(MONGODB_HOST, MONGODB_PORT)
db = mongo_client[MONGODB_NAME]
class MyApp(object):
def insert_event(self):
data = {'a': self.a, 'b': self.b} # some data
db.events.insert(data)
def request(self):
# request data processing...
spawn(self.insert_event)
return {}
app = Bottle()
app.route('/', method='POST')(MyApp().request)
And I want to test it with mongomock (https://github.com/vmalloc/mongomock).
from __future__ import unicode_literals
from unittest import TestCase
from webtest import TestApp
from mock import patch
from mongomock import MongoClient
from ..app import app as my_app
db = MongoClient().db
#patch('my_app.app.db', db)
class TestViews(TestCase):
def setUp(self):
self.app = TestApp(ssp_app)
self.db = db
def test_request(self):
response = self.app.post('/', {})
last_event = self.db.events.find_one({})
self.assertTrue(last_event)
My test fails.
FAIL: test_request (my_app.tests.TestViews)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/srv/mysite/my_app/tests/views.py", line 71, in test_request
self.assertTrue(last_event)
AssertionError: None is not true
It is work if I use self.insert_event without spawn. I tried to use patch.object, "with" statement, but without success...
I found solution. I need to mock gevent.spawn method. Because I get HTTP response before the coroutine ends. This my solution:
#patch('my_app.app.db', db)
#patch('my_app.app.spawn',
lambda method, *args, **kwargs: method(*args, **kwargs))
class TestViews(TestCase):

Tornado and Unicode

does tornado accept unicode in the adress?
#coding: utf-8 (there is # dont know how to show it here...)
import tornado.ioloop
import tornado.web
class Abdou(tornado.web.RequestHandler):
def get(self):
self.write("hi")
miaw = tornado.web.Application([
(u'/ééé', Abdou),
])
if __name__ == "__main__":
miaw.listen(8000)
tornado.ioloop.IOLoop
in Flask it worked !!!
from flask import Flask
miaw = Flask(__name__)
#miaw.route(u'/ééé')
def abdou():
return "hi!"
if __name__ == '__main__':
miaw.run()
NB: the same problem when using escape like /hello world , but in Flask it works!
NB2: thank you "wisty" for the edit :) now it appears more professional as a code :p
Look at tornado.escape.url_escape(value) and tornado.escape.url_unescape(value, encoding='utf-8').
Something like this:
#coding: utf-8 (there is # dont know how to show it here...)
import tornado.ioloop
import tornado.web
class Abdou(tornado.web.RequestHandler):
def get(self):
self.write("hi")
miaw = tornado.web.Application([
(tornado.escape.url_escape(u'/ééé'), Abdou),
])
if __name__ == "__main__":
miaw.listen(8000)
tornado.ioloop.IOLoop
You probably also want to be able to get urls that the user inputs. I think you do it like:
class Page(tornado.web.RequestHandler):
def get(self,title):
title = tornado.escape.url_unescape(title, encoding='utf-8')
self.write(title)
miaw = tornado.web.Application([
(tornado.escape.url_escape(u'/ééé/(*.)'), Page),
])
# you can get /ééé/page_name, where page_name can be unicode
if __name__ == "__main__":
miaw.listen(8000)
tornado.ioloop.IOLoop
it seems that it's a bug:
http://groups.google.com/group/python-tornado/browse_thread/thread/1f89cbeee05ba6fb/c028d3e4744eec8a?lnk=gst&q=unicode#c028d3e4744eec8a
and the link is dead :( the 404 is following me even here!

Categories

Resources