I would like to write unit test for web.py application by using pytest. How to invoke the web.py services in pytest.
Code:
import web
urls = (
'/', 'index'
)
app = web.application(urls, globals())
class index:
def GET(self):
return "Hello, world!"
if __name__ == "__main__":
app.run()
It can be done by using python requests module, when we run the web.py services, it will run http://localhost:8080/. Then import requests module and use get method and in response object, you can verify the result. That's fine.
By using Paste and nose also we can achieve this as per web.py official documentation. http://webpy.org/docs/0.3/tutorial.
Is there any solution in pytest like option in paste and nose.
Yes. Actually the code from the web.py recipe Testing with Paste and Nose could be used with py.test almost as is, just removing the nose.tools import and updating assertions appropriately.
But if you want to know how to write tests for web.py applications in the py.test style, they could look like this:
from paste.fixture import TestApp
# I assume the code from the question is saved in a file named app.py,
# in the same directory as the tests. From this file I'm importing the variable 'app'
from app import app
def test_index():
middleware = []
test_app = TestApp(app.wsgifunc(*middleware))
r = test_app.get('/')
assert r.status == 200
assert 'Hello, world!' in r
As you will add further tests, you will probably refactor creation of the test app out to a fixture:
from pytest import fixture # added
from paste.fixture import TestApp
from app import app
def test_index(test_app):
r = test_app.get('/')
assert r.status == 200
assert 'Hello, world!' in r
#fixture()
def test_app():
middleware = []
return TestApp(app.wsgifunc(*middleware))
Related
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.
A brief introduction to the directory structure is as follows:
__init__.py contain the application factory.
page.py
from app import app
# a simple page that says hello
#app.route('/hello')
def hello():
return 'Hello, World!'
app.py
from flaskr import create_app
app = create_app()
if __name__ == '__main__':
app.run()
When I start the server and go to the '/hello', it says 404.
What's the problem?
Here is a short solution which should run.
page.py
from flaskr import app
#app.route('/hello')
def hello():
return 'Hello'
__index__.py
from flask import Flask
app = Flask(__name__)
from flaskr import page
app.py
from flaskr import app
To run this you just need to define a Environment Variable on the Commandline like this:
export FLASK_APP=microblog.py
And then run it with
flask run
The way you have structured your code is not right. That's the reason you are not able to access your "/hello" API.
Let's assume that you run the app.py file. In this case, you haven't imported page.py right? So, how will the app.py know that there is a route defined in page.py?
Alternatively, let's assume you run page.py. In this case, when you do an "from app import app" , the main definition does not get executed. Here too, the route will now be present, but the app will not be run, thereby you will be unable to access your API.
The simplest solution would be to combine the contents of app.py and page.py into a single file.
I'm using Py.Test to test functions in a Python Flask app.
I have the tests passing fine when I use one "app_test.py" file that contains all the fixtures and tests. Now that I've split the fixtures into their own module, and separated the tests into different modules which each import the fixtures module I'm running into issues.
If I run tests on each module individually, everything passes fine:
pytest tests/test_1.py, pytest tests/test_2.py, pytest tests/test_3.py, etc. However trouble starts if I want to run all the tests in sequence with one command, e.g. pytest tests.
I get the first module of tests passing and all future ones report an error:
AssertionError: A setup function was called after the first request was handled.
This usually indicates a bug in the application where a module was not imported
and decorators or other functionality was called too late.
E To fix this make sure to import all your view modules, database models
and everything related at a central place before the application starts
serving requests.
All the tests and fixtures in one file looks something like this:
# app_test.py
from flask_app import create_app
#pytest.fixtures(scope="session")
def app(request):
app = create_app()
with app.app_context():
yield app
#pytest.fixture(scope="session"):
def client(request, app):
client = app.test_client()
return client
def test1(client):
# test body
def test2(client):
# test body
...
I run $ pytest app_test.py and everything runs perfectly.
Now let's say we split those into three different modules: fixures.py, test_1.py and test_2.py. The code now looks like this.
# tests/fixtures.py
from flask_app import create_app
#pytest.fixtures(scope="session")
def app(request):
app = create_app()
with app.app_context():
yield app
#pytest.fixture(scope="session"):
def client(request, app):
client = app.test_client()
return client
# tests/test_1.py
from tests.fixtures import app, client
def test_1(client):
# test body
# tests/test_2.py
from tests.fixtures import app, client
def test_2(client):
# test body
If we run $ pytest tests then tests/test_1.py will pass, and tests/test_2.py will raise the error.
I've looked at this gist, and have tried tagging the test functions with #pytest.mark.usefixture with no success.
How can one run Py.Test with modularized fixtures on a directory that contains multiple test files?
You use the fixtures slightly improperly.
Specifically, you declare multiple fixtures of the same name and with the same function-object, but detected at different modules. From the pytest point of view, these should be the separate functions, because pytest does dir(module) to detect the tests & fixtures in the file. But somehow due to the same function-object, I think, pytest remembers them as the same; so once you get to the second test file, it tries the locally detected fixture name, but finds it already prepared and fails.
The proper use would be to create a pseudo-plugin conftest.py, and put all the fixtures there. Once declared, these fixtures will be available to all files in that dir and all sub-dirs.
Note: such fixtures must NOT be imported into the test files. The fixtures are NOT the functions. Pytest automatically prepares them and feeds them to the tests.
# tests/conftest.py
import pytest
from flask_app import create_app
#pytest.fixtures(scope="session")
def app(request):
app = create_app()
with app.app_context():
yield app
#pytest.fixture(scope="session")
def client(request, app):
client = app.test_client()
return client
And the test files (note the absence of the imports!):
# tests/test_1.py
def test_1(client):
# test body
# tests/test_2.py
def test_2(client):
# test body
See more: https://docs.pytest.org/en/latest/writing_plugins.html
If you are using an application instance then this will work for you:
import os
import tempfile
import pytest
from my_app import app, db
#pytest.fixture
def client():
db_fd, app.config["DATABASE"] = tempfile.mkstemp()
app.config["TESTING"] = True
with app.app_context():
db.init_app(app)
yield app
os.close(db_fd)
os.unlink(app.config["DATABASE"])
#pytest.fixture
def client(request):
client = app.test_client()
return client
I'm having a problem with Flask wherein routes declared in imported modules are not be registered and always result in a 404. I am running the latest version Flask on Python 2.7.
I have the following directory structure:
run.py has the following code:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
import views.home
if __name__ == '__main__':
app.run()
home.py has the following code:
from run import app
#app.route('/test')
def test():
return "test"
When I run run.py the route declared in home.py (http://localhost:5000/test) always returns a 404 even though run.py imports views.home. The root view (http://localhost:5000) declared in run.py works fine.
I have written a function that prints out all the registered routes and /test is not in there (get a list of all routes defined in the app).
Any idea why?
I have discovered that switching the import statement in run.py from
import views.home
to
from views.home import *
makes everything work, which gave me the clue as to why the modules are not being registered using import views.home.
Basically, when run.py is run as a script it is given the name __main__ and this is the name given to the module in sys.modules (Importing modules: __main__ vs import as module)
Then when I import app from run.py in views.home.py a new instance of run.py is registered in sys.modules with the name run. As this point, the reference to app in run.py and views.home.py are two different references hence the routes not being registered.
The solution was to move the creation of the app variable out of run.py and in to a separate python file (I called it web_app.py) that is imported into run.py. This guarantees that the Flask app variable declared inside web_app.py is the always referenced correctly wherever web_app.py is imported.
So run.py now looks like this:
from web_app import app
if __name__ == '__main__':
app.run()
and web_app.py looks like this:
from flask import Flask
app = Flask(__name__)
import view.home
You can do it by reorganizing your code as it is described here Larger Applications, but it is more recommended to divide them into smaller groups where each group is implemented with the help of a blueprint. For a gentle introduction into this topic refer to the Modular Applications with Blueprints chapter of the documentation.
Modular Applications with Blueprints
I can't get it to work to use one module that creates the Flask application object and runs it, and one module that implements the views (routes and errorhandlers). The modules are not contained in a Python package.
app.py
from flask import Flask
app = Flask('graphlog')
import config
import views
if __name__ == '__main__':
app.run(host=config.host, port=config.port, debug=config.debug)
views.py
from app import app
#app.route('/')
def index():
return 'Hello!'
config.py
host = 'localhost'
port = 8080
debug = True
I always get Flask's default "404 Not Found" page. If I move the contents of view.py to app.py however, it works. What's the problem here?
You have four modules here:
__main__, the main script, the file you gave to the Python command to run.
config, loaded from the config.py file.
views, loaded from the views.py file.
app, loaded from app.py when you use import app.
Note that the latter is separate from the first! The initial script is not loaded as app and Python sees it as different. You have two Flask objects, one referenced as __main__.app, the other as app.app.
Create a separate file to be the main entry point for your script; say run.py:
from app import app
import config
if __name__ == '__main__':
app.run(host=config.host, port=config.port, debug=config.debug)
and remove the import config line from app.py, as well as the last two lines.
Alternatively (but much uglier), use from __main__ import app in views.py.