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.
Related
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
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
Suppose we have the following Flask view function to test. Specifically, we want to mock create_foo() as it writes to the filesystem.
# proj_root/some_project/views.py
from some_project import APP
from some_project.libraries.foo import create_foo
#APP.route('/run', methods=['POST']
def run():
bar = create_foo()
return 'Running'
Now we want to write a unit test for run() with the call to create_foo() mocked to avoid creating unnecessary files.
# proj_root/tests/test_views.py
from some_project import some_project
#pytest.fixture
def client():
some_project.APP.config['TESTING'] = True
with some_project.APP.test_client() as client:
yield client
def test_run(client, monkeypatch):
monkeypatch.setattr('some_project.libraries.foo.create_foo', lambda: None)
response = client.post('/run')
assert b'Running' in response.data
It seems that this approach should work even with the named create_foo import. The tests all pass, however the original code of create_foo is clearly being executed as a new file in the filesystem is created each time the test suite is run. What am I missing? I suspect it has something to do with the named imports based on some related questions but I'm not sure.
The correct monkeypatch is:
monkeypatch.setattr('some_project.views.create_foo', lambda: None)
The reason for this is pretty well explained here.
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))
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