flask-unittest


Nameflask-unittest JSON
Version 0.1.3 PyPI version JSON
download
home_pagehttps://github.com/TotallyNotChase/flask-unittest
SummaryUnit testing flask applications made easy!
upload_time2022-06-05 06:45:12
maintainer
docs_urlNone
authorTotallyNotChase
requires_python>=3.6
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # flask-unittest
A hassle free solution to testing flask application using `unittest`

Provides functionality for testing using the `Flask` object, the `FlaskClient` object, a combination of the two, or even a live flask server!

This library is intended to provide utilities that help the user follow the [official flask application testing guidelines](https://flask.palletsprojects.com/en/1.1.x/testing/). It is recommended you familiarize yourself with that page.

Unless you're interested in testing a live flask server using a headless browser. In which case, familiarity with your preferred headless browser is enough.

# Features
* Test flask applications using a `Flask` object
  * Access to `app_context`, `test_request_context` etc
  * Access to flask globals like `g`, `request`, and `session`
  * Access to `test_client` through the `Flask` object
  * Same `Flask` object will be usable in the test method and its corresponding `setUp` and `tearDown` methods
  * App is created per test method in the testcase
* Test flask applications using a `FlaskClient` object
  * Access to flask globals like `g`, `request`, and `session`
  * Test your flask app in an **API centric way** using the functionality provided by `FlaskClient`
  * Same `FlaskClient` object will be usable in the test method and its corresponding `setUp` and `tearDown` methods
  * The `FlaskClient` is created per test method of the testcase by using the given `Flask` object (App)
  * App can either be a constant class property throughout the testcase, or be created per test method
* Test flask applications running *live* on localhost - using your preferred **headless browser** (e.g `selenium`, `pyppeteer` etc)
  * Contrary to the previous ones, this functionality is handled by a test suite, rather than a test case
  * The flask server is started in a daemon thread when the `LiveTestSuite` runs - it runs for the duration of the program
* Simple access to the context so you can access flask globals (`g`, `request`, and `session`) with minimal headaches and no gotchas!
* Support for using generators as `create_app` - essentially emulating `pytest`'s fixtures (more of that in `example/tests/`)
* No extra dependencies! (well, except for `flask`...) - easily integratable with the built in `unittest` module

# Quick Start
Install `flask-unittest` from pypi using `pip`
```bash
pip install flask-unittest
```

Import in your module and start testing!
```py
import flask_unittest
```

Now, before moving on to the examples below - I **highly recommend** checking out the official [Testing Flask Applications example](https://flask.palletsprojects.com/en/1.1.x/testing/). It's *extremely simple* and should take only 5 minutes to digest.

Alternatively, you can directly dive into the examples at [`tests/`](./tests/) and [`example/tests/`](./example/tests). Though this might be a bit intimidating if you're just starting out at testing flask apps.

**NOTE**: For all the following testcases using `FlaskClient`, it is recommended to set `.testing` on your `Flask` app to `True` (i.e `app.testing = True`)

# Test using `FlaskClient`
If you want to use a [`FlaskClient`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.test_client) object to test - this is the testcase for you!

This testcase creates a `FlaskClient` object for each test method. But the `app` property is kept constant.
```py
import flask_unittest
import flask.globals

class TestFoo(flask_unittest.ClientTestCase):
    # Assign the `Flask` app object
    app = ...

    def setUp(self, client):
        # Perform set up before each test, using client
        pass

    def tearDown(self, client):
        # Perform tear down after each test, using client
        pass

    '''
    Note: the setUp and tearDown method don't need to be explicitly declared
    if they don't do anything (like in here) - this is just an example
    Only declare the setUp and tearDown methods with a body, same as regular unittest testcases
    '''

    def test_foo_with_client(self, client):
        # Use the client here
        # Example request to a route returning "hello world" (on a hypothetical app)
        rv = client.get('/hello')
        self.assertInResponse(rv, 'hello world!')

    def test_bar_with_client(self, client):
        # Use the client here
        # Example login request (on a hypothetical app)
        rv = client.post('/login', {'username': 'pinkerton', 'password': 'secret_key'})
        # Make sure rv is a redirect request to index page
        self.assertLocationHeader('http://localhost/')
        # Make sure session is set
        self.assertIn('user_id', flask.globals.session)
```
Remember to assign a correctly configured `Flask` app object to `app`!

Each test method, as well as the `setUp` and `tearDown` methods, should take `client` as a parameter. You can name this parameter whatever you want of course but the 2nd parameter (including `self` as first) is a `FlaskClient` object.

Note that the `client` is different for *each test method*. But it's the same for a singular test method and its corresponding `setUp` and `tearDown` methods.

What does this mean? Well, when it's time to run `test_foo_with_client`, a `FlaskClient` object is created using `app.test_client()`. Then this is passed to `setUp`, which does its job of setup. After that, the same `client` is passed to `test_foo_with_client`, which does the testing. Finally, the same `client` again, is passed to `tearDown` - which cleans the stuff up.

Now when it's time to run `test_bar_with_client`, a new `FlaskClient` object is created and so on.

This essentially means that any global changes (such as `session` and cookies) you perform in `setUp` using `client`, will be persistent in the actual test method. And the changes in the test method will be persistent in the `tearDown`. These changes get destroyed in the next test method, where a new `FlaskClient` object is created.

**NOTE**: If you want to **disable** the use of cookies on `client`, you need to put `test_client_use_cookies = False` in your testcase body.

You can also pass in extra kwargs to the `test_client()` call by setting `test_client_kwargs` in your testcase body.

**Full Example**: [`flask_client_test.py`](./tests/flask_client_test.py)

# Test using `Flask`
If you want to use a [`Flask`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask) object to test - this is the testcase for you!

This testcase creates a `Flask` object for each test method, using the `create_app` method implemented by the user
```py
import flask_unittest
from flaskr.db import get_db

class TestFoo(flask_unittest.AppTestCase):

    def create_app(self):
        # Return/Yield a `Flask` object here
        pass

    def setUp(self, app):
        # Perform set up before each test, using app
        pass

    def tearDown(self, app):
        # Perform tear down after each test, using app
        pass

    '''
    Note: the setUp and tearDown method don't need to be explicitly declared
    if they don't do anything (like in here) - this is just an example
    Only declare the setUp and tearDown methods with a body, same as regular unittest testcases
    '''

    def test_foo_with_app(self, app):
        # Use the app here
        # Example of using test_request_context (on a hypothetical app)
        with app.test_request_context('/1/update'):
            self.assertEqual(request.endpoint, 'blog.update')

    def test_bar_with_app(self, app):
        # Use the app here
        # Example of using client from app (on a hypothetical app)
        with app.test_client() as client:
            rv = client.get('/hello')
            self.assertInResponse(rv, 'hello world!')

    def test_baz_with_app(self, app):
        # Use the app here
        # Example of using app_context (on a hypothetical app)
        with app.app_context():
            get_db().execute("INSERT INTO user (username, password) VALUES ('test', 'testpass');")
```
The `create_app` function should return a correctly configured `Flask` object representing the webapp to test

You can also do any set up, extra config for the app (db init etc) here

It's also possible (and encouraged) to `yield` a `Flask` object here instead of `return`ing one (essentially making this a generator function)
This way, you can put cleanup right here after the `yield` and they will be executed once the test method has run

See [Emulating official flask testing example using `flask-unittest`](#emulating-official-flask-testing-example-using-flask-unittest)

Each test method, as well as the `setUp` and `tearDown` methods, should take `app` as a parameter. You can name this parameter whatever you want of course but the 2nd parameter (including `self` as first) is a `Flask` object returned/yielded from the user provided `create_app`.

Note that the `app` is different for *each test method*. But it's the same for a singular test method and its corresponding `setUp` and `tearDown` methods.

What does this mean? Well, when it's time to run `test_foo_with_app`, a `Flask` object is created using `create_app`. Then this is passed to `setUp`, which does its job of setup. After that, the same `app` is passed to `test_foo_with_app`, which does the testing. Finally, the same `app` again, is passed to `tearDown` - which cleans the stuff up.

Now when it's time to run `test_bar_with_app` - `create_app` is called again and a new `Flask` object is created and so on.

If `create_app` is a generator function. All the stuff after `yield app` will be executed after the test method (and its `tearDown`, if any) has run

**Full Example**: [`flask_app_test.py`](./tests/flask_app_test.py)

# Test using both `Flask` and `FlaskClient`
If you want to use both [`Flask`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask) *and* [`FlaskClient`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.test_client) to test - this is the testcase for you!

This testcase creates a `Flask` object, using the `create_app` method implemented by the user, *and* a `FlaskClient` object from said `Flask` object, for each test method
```py
import flask_unittest
from flaskr import get_db

class TestFoo(flask_unittest.AppClientTestCase):

    def create_app(self):
        # Return/Yield a `Flask` object here
        pass

    def setUp(self, app, client):
        # Perform set up before each test, using app and client
        pass

    def tearDown(self, app, client):
        # Perform tear down after each test, using app and client
        pass

    '''
    Note: the setUp and tearDown method don't need to be explicitly declared
    if they don't do anything (like in here) - this is just an example
    Only declare the setUp and tearDown methods with a body, same as regular unittest testcases
    '''

    def test_foo_with_both(self, app, client):
        # Use the app and client here
        # Example of registering a user and checking if the entry exists in db (on a hypothetical app)
        response = client.post('/auth/register', data={'username': 'a', 'password': 'a'})
        self.assertLocationHeader(response, 'http://localhost/auth/login')

        # test that the user was inserted into the database
        with app.app_context():
            self.assertIsNotNone(get_db().execute("select * from user where username = 'a'").fetchone())

    def test_bar_with_both(self, app, client):
        # Use the app and client here
        # Example of creating a post and checking if the entry exists in db (on a hypothetical app)
        client.post('/create', data={'title': 'created', 'body': ''})

        with app.app_context():
            db = get_db()
            count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0]
            self.assertEqual(count, 2)
```
The `create_app` function should return a correctly configured `Flask` object representing the webapp to test

You can also do any set up, extra config for the app (db init etc) here

It's also possible (and encouraged) to `yield` a `Flask` object here instead of `return`ing one (essentially making this a generator function)
This way, you can put cleanup right here after the `yield` and they will be executed once the test method has run

See [Emulating official flask testing example using `flask-unittest`](#emulating-official-flask-testing-example-using-flask-unittest)

Each test method, as well as the `setUp` and `tearDown` methods, should take `app` and `client` as a parameter. You can name these parameters whatever you want of course but the 2nd parameter (including `self` as first) is a `Flask` object returned/yielded from the user provided `create_app`, and the third parameter is a `FlaskClient` object returned from calling `.test_client` on said `Flask` object.

Note that the `app` and `client` are different for *each test method*. But they are the same for a singular test method and its corresponding `setUp` and `tearDown` methods.

What does this mean? Well, when it's time to run `test_foo_with_both`, a `Flask` object is created using `create_app()`, and a `FlaskClient` object is created from it. Then they are passed to `setUp`, which does its job of setup. After that, the same `app` and `client` are passed to `test_foo_with_both`, which does the testing. Finally, the same `app` and `client` again, are passed to `tearDown` - which cleans the stuff up.

Now when it's time to run `test_bar_with_app` - `create_app` is called again to create a new `Flask` object, and also `.test_client` to create a new `FlaskClient` object and so on.

If `create_app` is a generator function. All the stuff after `yield app` will be executed after the test method (and its `tearDown` if any) has run

**Full Example**: [`flask_appclient_test.py`](./tests/flask_appclient_test.py)

# Test using a headless browser (eg `selenium`, `pyppeteer` etc)
If you want to test a live flask server using a headless browser - `LiveTestSuite` is for you!

Unlike the previous ones, this functionality relies on the use of a **suite**, *not a testcase*. The testcases should inherit from `LiveTestCase` but the real juice is in `LiveTestSuite`.

An example testcase for this would look like-
```py
import flask_unittest
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class TestFoo(flask_unittest.LiveTestCase):
    driver: Union[Chrome, None] = None
    std_wait: Union[WebDriverWait, None] = None

    ### setUpClass and tearDownClass for the entire class
    # Not quite mandatory, but this is the best place to set up and tear down selenium

    @classmethod
    def setUpClass(cls):
        # Initiate the selenium webdriver
        options = ChromeOptions()
        options.add_argument('--headless')
        cls.driver = Chrome(options=options)
        cls.std_wait = WebDriverWait(cls.driver, 5)

    @classmethod
    def tearDownClass(cls):
        # Quit the webdriver
        cls.driver.quit()

    ### Actual test methods

    def test_foo_with_driver(self):
        # Use self.driver here
        # You also have access to self.server_url and self.app
        # Example of using selenium to go to index page and try to find some elements (on a hypothetical app)
        self.driver.get(self.server_url)
        self.std_wait.until(EC.presence_of_element_located((By.LINK_TEXT, 'Register')))
        self.std_wait.until(EC.presence_of_element_located((By.LINK_TEXT, 'Log In')))
```
This is pretty straight forward, it's just a regular test case that you would use if you spawned the flask server from the terminal before running tests

Now, you need to use the `LiveTestSuite` to run this. The previous testcases could be run using `unitttest.TestSuite`, or simply `unittest.main` but this *has to be* run using the custom suite
```py
# Assign the flask app here
app = ...

# Add TestFoo to suite
suite = flask_unittest.LiveTestSuite(app)
suite.addTest(unittest.makeSuite(TestFoo))

# Run the suite
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
```
The `LiveTestSuite` requires a built and configured `Flask` app object. It'll spawn this flask app using `app.run` as a daemon thread.

By default, the app runs on host 127.0.0.1 and port 5000. If you'd like to change this assign your custom host (as a `str`) and a port (as an `int`) to `app.config` under the key `HOST` and `PORT` respectively. (`app.config['HOST'] = '0.0.0.0'; app.config['PORT'] = 7000`)

The server is started when the suite is first run and it runs for the duration of the program

You will have access to the `app` passed to the suite inside `LiveTestCase`, using `self.app`. You will also have access to the url the server is running on inside the testcase, using `self.server_url`

**Full Example** (of `LiveTestCase`): [`flask_live_test.py`](./tests/flask_live_test.py)
**Full Example** (of `LiveTestSuite`): [`__init__.py`](./tests/__init__.py)

# About request context and flask globals
Both `ClientTestCase` and `AppClientTestCase` allow you to use flask gloabls, such as `request`, `g`, and `session`, directly in your test method (and your `setUp` and `tearDown` methods)

This is because the `client` is *instantiated using a `with` block*, and the test method, the `setUp` and `tearDown` methods **happen inside the `with` block**

Very rough psuedocode representation of this would look like-
```py
with app.test_client() as client:
    self.setUp(client)
    self.test_method(client)
    self.tearDown(client)
```
This means you can treat everything in your test method, and `setUp` and `tearDown` methods, as if they are within a `with client:` block

Practically, this lets you use the flask globals after making a request using `client` - which is great for testing

Additional info in the [official docs](https://flask.palletsprojects.com/en/1.1.x/testing/#keeping-the-context-around)

# Emulating official flask testing example using `flask-unittest`
The official flask testing example can be found [in the flask repo](https://github.com/pallets/flask/tree/master/examples/tutorial/tests)

The original tests are written using `pytest`. This example demonstrates how `flask-unittest` can provide the same functionality for you, with the same degree of control!

Note that this demonstration does not implement the `test_cli_runner` - since that is not directly supported by `flask-unittest` (yet). However, it's completely possible to simply use `.test_cli_runner()` on the `app` object in the testcases provided by `flask-unittest` to emulate this.

The primary thing to demonstrate here, is to emulate the pytest fixtures defined in the original [`conftest.py`](https://github.com/pallets/flask/blob/master/examples/tutorial/tests/conftest.py)-
```py
@pytest.fixture
def app():
    """Create and configure a new app instance for each test."""
    # create a temporary file to isolate the database for each test
    db_fd, db_path = tempfile.mkstemp()
    # create the app with common test config
    app = create_app({"TESTING": True, "DATABASE": db_path})

    # create the database and load test data
    with app.app_context():
        init_db()
        get_db().executescript(_data_sql)

    yield app

    # close and remove the temporary database
    os.close(db_fd)
    os.unlink(db_path)


@pytest.fixture
def client(app):
    """A test client for the app."""
    return app.test_client()
```

As you can see, this creates the app **and** the test client *per test*. So we'll be using `AppClientTestCase` for this.

Let's make a base class that provides functionality for this - all the other testcases can inherit from it. Defined in [`conftest.py`](./example/tests/conftest.py)

```py
import flask_unittest


class TestBase(flask_unittest.AppClientTestCase):

    def create_app(self):
        """Create and configure a new app instance for each test."""
        # create a temporary file to isolate the database for each test
        db_fd, db_path = tempfile.mkstemp()
        # create the app with common test config
        app = create_app({"TESTING": True, "DATABASE": db_path})

        # create the database and load test data
        with app.app_context():
            init_db()
            get_db().executescript(_data_sql)

            # Yield the app
            '''
            This can be outside the `with` block too, but we need to 
            call `close_db` before exiting current context
            Otherwise windows will have trouble removing the temp file
            that doesn't happen on unices though, which is nice
            '''
            yield app

            ## Close the db
            close_db()
        
        ## Cleanup temp file
        os.close(db_fd)
        os.remove(db_path)
```

This is very similar to the original pytest fixtures and achieves the exact same functionality in the actual testcases too!

Do note however, there's an extra call inside `with app.app_context()` - `close_db`. Windows struggles to remove the temp database using `os.remove` if it hasn't been closed already - so we have to do that (this is true for the original pytest methods too).

Also of note, creation of the `AuthActions` object should be handled manually in each of the test case. This is just how `unittest` works in contrast to `pytest`. This shouldn't pose any issue whatsoever though.

Now let's look at an actual testcase. We'll be looking at `test_auth.py`, since it demonstrates the use of `app`, `client` and the flask globals very nicely.

For context, the original file is defined at [`test_auth.py`](https://github.com/pallets/flask/blob/master/examples/tutorial/tests/test_auth.py)

The full emulation of this file is at [`test_auth.py`](./example/tests/test_auth.py)

Ok! Let's look at the emulation of `test_register`.

For context, this is the original function-
```py
def test_register(client, app):
    # test that viewing the page renders without template errors
    assert client.get("/auth/register").status_code == 200

    # test that successful registration redirects to the login page
    response = client.post("/auth/register", data={"username": "a", "password": "a"})
    assert "http://localhost/auth/login" == response.headers["Location"]

    # test that the user was inserted into the database
    with app.app_context():
        assert (
            get_db().execute("select * from user where username = 'a'").fetchone()
            is not None
        )
```

And here's the `flask-unittest` version!
```py
from example.tests.conftest import AuthActions, TestBase


class TestAuth(TestBase):

    def test_register(self, app, client):
        # test that viewing the page renders without template errors
        self.assertStatus(client.get("/auth/register"), 200)

        # test that successful registration redirects to the login page
        response = client.post("/auth/register", data={"username": "a", "password": "a"})
        self.assertLocationHeader(response, "http://localhost/auth/login")

        # test that the user was inserted into the database
        with app.app_context():
            self.assertIsNotNone(
                get_db().execute("select * from user where username = 'a'").fetchone()
            )
```
See how similar it is? The only difference is that the function should be a method in a class that is extending `flask_unittest.AppClientTestCase` with `create_app` defined. In our case, that's `TestBase` from `conftest.py` - so we extend from that.

As mentioned previously, each test method of an `AppClientTestCase` should have the parameters `self, app, client` - not necessarily with the same names but the second param **will be** the `Flask` object, and the third param **will be** the `FlaskClient` object

Also, this is using `self.assert...` functions as per `unittest` convention. However, regular `assert`s should work just fine.

Nice! Let's look at a function that uses flask globals - `test_login`

Here's the original snippet-
```py
def test_login(client, auth):
    # test that viewing the page renders without template errors
    assert client.get("/auth/login").status_code == 200

    # test that successful login redirects to the index page
    response = auth.login()
    assert response.headers["Location"] == "http://localhost/"

    # login request set the user_id in the session
    # check that the user is loaded from the session
    with client:
        client.get("/")
        assert session["user_id"] == 1
        assert g.user["username"] == "test"

```

And here's the `flask-unittest` version-
```py
class TestAuth(TestBase):
    
    def test_login(self, _, client):
        # test that viewing the page renders without template errors
        self.assertStatus(client.get("/auth/login"), 200)

        # test that successful login redirects to the index page
        auth = AuthActions(client)
        response = auth.login()
        self.assertLocationHeader(response, "http://localhost/")

        # login request set the user_id in the session
        # check that the user is loaded from the session
        client.get("/")
        self.assertEqual(session["user_id"], 1)
        self.assertEqual(g.user["username"], "test")
```

(this is a continuation of the previous example for `test_register`)

Once again, very similar. But there's a couple of things to note here.

Firstly, notice we are ignoring the second argument of `test_login`, since we have no reason to use `app` here. We do, however, need to use the `FlaskClient` object

Also notice, we don't have to do `with client` to access the request context. `flask-unittest` already handles this for us, so we have direct access to `session` and `g`.

Let's check out a case where we only use the `Flask` object and not the `FlaskClient` object - in which case, we can use `AppTestCase`.

The original function, `test_get_close_db`, is defined at [`test_db.py`](https://github.com/pallets/flask/blob/master/examples/tutorial/tests/test_db.py)

```py
def test_get_close_db(app):
    with app.app_context():
        db = get_db()
        assert db is get_db()

    with pytest.raises(sqlite3.ProgrammingError) as e:
        db.execute("SELECT 1")

    assert "closed" in str(e.value)
```

The `flask-unittest` version can be seen at [`test_db.py`](./example/tests/test_db.py)

```py
import flask_unittest

class TestDB(flask_unittest.AppTestCase):

   # create_app omitted for brevity - remember to include it!
   
    def test_get_close_db(self, app):
        with app.app_context():
            db = get_db()
            assert db is get_db()

        try:
            db.execute("SELECT 1")
        except sqlite3.ProgrammingError as e:
            self.assertIn("closed", str(e.args[0]))

```

Very similar once again!

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/TotallyNotChase/flask-unittest",
    "name": "flask-unittest",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": "",
    "keywords": "",
    "author": "TotallyNotChase",
    "author_email": "totallynotchase42@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/ce/65/8858ad436da94463af0c36bbbf9c42a893d0cfe0c788f6ab64dba739c86b/flask-unittest-0.1.3.tar.gz",
    "platform": "any",
    "description": "# flask-unittest\nA hassle free solution to testing flask application using `unittest`\n\nProvides functionality for testing using the `Flask` object, the `FlaskClient` object, a combination of the two, or even a live flask server!\n\nThis library is intended to provide utilities that help the user follow the [official flask application testing guidelines](https://flask.palletsprojects.com/en/1.1.x/testing/). It is recommended you familiarize yourself with that page.\n\nUnless you're interested in testing a live flask server using a headless browser. In which case, familiarity with your preferred headless browser is enough.\n\n# Features\n* Test flask applications using a `Flask` object\n  * Access to `app_context`, `test_request_context` etc\n  * Access to flask globals like `g`, `request`, and `session`\n  * Access to `test_client` through the `Flask` object\n  * Same `Flask` object will be usable in the test method and its corresponding `setUp` and `tearDown` methods\n  * App is created per test method in the testcase\n* Test flask applications using a `FlaskClient` object\n  * Access to flask globals like `g`, `request`, and `session`\n  * Test your flask app in an **API centric way** using the functionality provided by `FlaskClient`\n  * Same `FlaskClient` object will be usable in the test method and its corresponding `setUp` and `tearDown` methods\n  * The `FlaskClient` is created per test method of the testcase by using the given `Flask` object (App)\n  * App can either be a constant class property throughout the testcase, or be created per test method\n* Test flask applications running *live* on localhost - using your preferred **headless browser** (e.g `selenium`, `pyppeteer` etc)\n  * Contrary to the previous ones, this functionality is handled by a test suite, rather than a test case\n  * The flask server is started in a daemon thread when the `LiveTestSuite` runs - it runs for the duration of the program\n* Simple access to the context so you can access flask globals (`g`, `request`, and `session`) with minimal headaches and no gotchas!\n* Support for using generators as `create_app` - essentially emulating `pytest`'s fixtures (more of that in `example/tests/`)\n* No extra dependencies! (well, except for `flask`...) - easily integratable with the built in `unittest` module\n\n# Quick Start\nInstall `flask-unittest` from pypi using `pip`\n```bash\npip install flask-unittest\n```\n\nImport in your module and start testing!\n```py\nimport flask_unittest\n```\n\nNow, before moving on to the examples below - I **highly recommend** checking out the official [Testing Flask Applications example](https://flask.palletsprojects.com/en/1.1.x/testing/). It's *extremely simple* and should take only 5 minutes to digest.\n\nAlternatively, you can directly dive into the examples at [`tests/`](./tests/) and [`example/tests/`](./example/tests). Though this might be a bit intimidating if you're just starting out at testing flask apps.\n\n**NOTE**: For all the following testcases using `FlaskClient`, it is recommended to set `.testing` on your `Flask` app to `True` (i.e `app.testing = True`)\n\n# Test using `FlaskClient`\nIf you want to use a [`FlaskClient`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.test_client) object to test - this is the testcase for you!\n\nThis testcase creates a `FlaskClient` object for each test method. But the `app` property is kept constant.\n```py\nimport flask_unittest\nimport flask.globals\n\nclass TestFoo(flask_unittest.ClientTestCase):\n    # Assign the `Flask` app object\n    app = ...\n\n    def setUp(self, client):\n        # Perform set up before each test, using client\n        pass\n\n    def tearDown(self, client):\n        # Perform tear down after each test, using client\n        pass\n\n    '''\n    Note: the setUp and tearDown method don't need to be explicitly declared\n    if they don't do anything (like in here) - this is just an example\n    Only declare the setUp and tearDown methods with a body, same as regular unittest testcases\n    '''\n\n    def test_foo_with_client(self, client):\n        # Use the client here\n        # Example request to a route returning \"hello world\" (on a hypothetical app)\n        rv = client.get('/hello')\n        self.assertInResponse(rv, 'hello world!')\n\n    def test_bar_with_client(self, client):\n        # Use the client here\n        # Example login request (on a hypothetical app)\n        rv = client.post('/login', {'username': 'pinkerton', 'password': 'secret_key'})\n        # Make sure rv is a redirect request to index page\n        self.assertLocationHeader('http://localhost/')\n        # Make sure session is set\n        self.assertIn('user_id', flask.globals.session)\n```\nRemember to assign a correctly configured `Flask` app object to `app`!\n\nEach test method, as well as the `setUp` and `tearDown` methods, should take `client` as a parameter. You can name this parameter whatever you want of course but the 2nd parameter (including `self` as first) is a `FlaskClient` object.\n\nNote that the `client` is different for *each test method*. But it's the same for a singular test method and its corresponding `setUp` and `tearDown` methods.\n\nWhat does this mean? Well, when it's time to run `test_foo_with_client`, a `FlaskClient` object is created using `app.test_client()`. Then this is passed to `setUp`, which does its job of setup. After that, the same `client` is passed to `test_foo_with_client`, which does the testing. Finally, the same `client` again, is passed to `tearDown` - which cleans the stuff up.\n\nNow when it's time to run `test_bar_with_client`, a new `FlaskClient` object is created and so on.\n\nThis essentially means that any global changes (such as `session` and cookies) you perform in `setUp` using `client`, will be persistent in the actual test method. And the changes in the test method will be persistent in the `tearDown`. These changes get destroyed in the next test method, where a new `FlaskClient` object is created.\n\n**NOTE**: If you want to **disable** the use of cookies on `client`, you need to put `test_client_use_cookies = False` in your testcase body.\n\nYou can also pass in extra kwargs to the `test_client()` call by setting `test_client_kwargs` in your testcase body.\n\n**Full Example**: [`flask_client_test.py`](./tests/flask_client_test.py)\n\n# Test using `Flask`\nIf you want to use a [`Flask`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask) object to test - this is the testcase for you!\n\nThis testcase creates a `Flask` object for each test method, using the `create_app` method implemented by the user\n```py\nimport flask_unittest\nfrom flaskr.db import get_db\n\nclass TestFoo(flask_unittest.AppTestCase):\n\n    def create_app(self):\n        # Return/Yield a `Flask` object here\n        pass\n\n    def setUp(self, app):\n        # Perform set up before each test, using app\n        pass\n\n    def tearDown(self, app):\n        # Perform tear down after each test, using app\n        pass\n\n    '''\n    Note: the setUp and tearDown method don't need to be explicitly declared\n    if they don't do anything (like in here) - this is just an example\n    Only declare the setUp and tearDown methods with a body, same as regular unittest testcases\n    '''\n\n    def test_foo_with_app(self, app):\n        # Use the app here\n        # Example of using test_request_context (on a hypothetical app)\n        with app.test_request_context('/1/update'):\n            self.assertEqual(request.endpoint, 'blog.update')\n\n    def test_bar_with_app(self, app):\n        # Use the app here\n        # Example of using client from app (on a hypothetical app)\n        with app.test_client() as client:\n            rv = client.get('/hello')\n            self.assertInResponse(rv, 'hello world!')\n\n    def test_baz_with_app(self, app):\n        # Use the app here\n        # Example of using app_context (on a hypothetical app)\n        with app.app_context():\n            get_db().execute(\"INSERT INTO user (username, password) VALUES ('test', 'testpass');\")\n```\nThe `create_app` function should return a correctly configured `Flask` object representing the webapp to test\n\nYou can also do any set up, extra config for the app (db init etc) here\n\nIt's also possible (and encouraged) to `yield` a `Flask` object here instead of `return`ing one (essentially making this a generator function)\nThis way, you can put cleanup right here after the `yield` and they will be executed once the test method has run\n\nSee [Emulating official flask testing example using `flask-unittest`](#emulating-official-flask-testing-example-using-flask-unittest)\n\nEach test method, as well as the `setUp` and `tearDown` methods, should take `app` as a parameter. You can name this parameter whatever you want of course but the 2nd parameter (including `self` as first) is a `Flask` object returned/yielded from the user provided `create_app`.\n\nNote that the `app` is different for *each test method*. But it's the same for a singular test method and its corresponding `setUp` and `tearDown` methods.\n\nWhat does this mean? Well, when it's time to run `test_foo_with_app`, a `Flask` object is created using `create_app`. Then this is passed to `setUp`, which does its job of setup. After that, the same `app` is passed to `test_foo_with_app`, which does the testing. Finally, the same `app` again, is passed to `tearDown` - which cleans the stuff up.\n\nNow when it's time to run `test_bar_with_app` - `create_app` is called again and a new `Flask` object is created and so on.\n\nIf `create_app` is a generator function. All the stuff after `yield app` will be executed after the test method (and its `tearDown`, if any) has run\n\n**Full Example**: [`flask_app_test.py`](./tests/flask_app_test.py)\n\n# Test using both `Flask` and `FlaskClient`\nIf you want to use both [`Flask`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask) *and* [`FlaskClient`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.test_client) to test - this is the testcase for you!\n\nThis testcase creates a `Flask` object, using the `create_app` method implemented by the user, *and* a `FlaskClient` object from said `Flask` object, for each test method\n```py\nimport flask_unittest\nfrom flaskr import get_db\n\nclass TestFoo(flask_unittest.AppClientTestCase):\n\n    def create_app(self):\n        # Return/Yield a `Flask` object here\n        pass\n\n    def setUp(self, app, client):\n        # Perform set up before each test, using app and client\n        pass\n\n    def tearDown(self, app, client):\n        # Perform tear down after each test, using app and client\n        pass\n\n    '''\n    Note: the setUp and tearDown method don't need to be explicitly declared\n    if they don't do anything (like in here) - this is just an example\n    Only declare the setUp and tearDown methods with a body, same as regular unittest testcases\n    '''\n\n    def test_foo_with_both(self, app, client):\n        # Use the app and client here\n        # Example of registering a user and checking if the entry exists in db (on a hypothetical app)\n        response = client.post('/auth/register', data={'username': 'a', 'password': 'a'})\n        self.assertLocationHeader(response, 'http://localhost/auth/login')\n\n        # test that the user was inserted into the database\n        with app.app_context():\n            self.assertIsNotNone(get_db().execute(\"select * from user where username = 'a'\").fetchone())\n\n    def test_bar_with_both(self, app, client):\n        # Use the app and client here\n        # Example of creating a post and checking if the entry exists in db (on a hypothetical app)\n        client.post('/create', data={'title': 'created', 'body': ''})\n\n        with app.app_context():\n            db = get_db()\n            count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0]\n            self.assertEqual(count, 2)\n```\nThe `create_app` function should return a correctly configured `Flask` object representing the webapp to test\n\nYou can also do any set up, extra config for the app (db init etc) here\n\nIt's also possible (and encouraged) to `yield` a `Flask` object here instead of `return`ing one (essentially making this a generator function)\nThis way, you can put cleanup right here after the `yield` and they will be executed once the test method has run\n\nSee [Emulating official flask testing example using `flask-unittest`](#emulating-official-flask-testing-example-using-flask-unittest)\n\nEach test method, as well as the `setUp` and `tearDown` methods, should take `app` and `client` as a parameter. You can name these parameters whatever you want of course but the 2nd parameter (including `self` as first) is a `Flask` object returned/yielded from the user provided `create_app`, and the third parameter is a `FlaskClient` object returned from calling `.test_client` on said `Flask` object.\n\nNote that the `app` and `client` are different for *each test method*. But they are the same for a singular test method and its corresponding `setUp` and `tearDown` methods.\n\nWhat does this mean? Well, when it's time to run `test_foo_with_both`, a `Flask` object is created using `create_app()`, and a `FlaskClient` object is created from it. Then they are passed to `setUp`, which does its job of setup. After that, the same `app` and `client` are passed to `test_foo_with_both`, which does the testing. Finally, the same `app` and `client` again, are passed to `tearDown` - which cleans the stuff up.\n\nNow when it's time to run `test_bar_with_app` - `create_app` is called again to create a new `Flask` object, and also `.test_client` to create a new `FlaskClient` object and so on.\n\nIf `create_app` is a generator function. All the stuff after `yield app` will be executed after the test method (and its `tearDown` if any) has run\n\n**Full Example**: [`flask_appclient_test.py`](./tests/flask_appclient_test.py)\n\n# Test using a headless browser (eg `selenium`, `pyppeteer` etc)\nIf you want to test a live flask server using a headless browser - `LiveTestSuite` is for you!\n\nUnlike the previous ones, this functionality relies on the use of a **suite**, *not a testcase*. The testcases should inherit from `LiveTestCase` but the real juice is in `LiveTestSuite`.\n\nAn example testcase for this would look like-\n```py\nimport flask_unittest\nfrom selenium.webdriver import Chrome, ChromeOptions\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.ui import WebDriverWait\nfrom selenium.webdriver.support import expected_conditions as EC\n\nclass TestFoo(flask_unittest.LiveTestCase):\n    driver: Union[Chrome, None] = None\n    std_wait: Union[WebDriverWait, None] = None\n\n    ### setUpClass and tearDownClass for the entire class\n    # Not quite mandatory, but this is the best place to set up and tear down selenium\n\n    @classmethod\n    def setUpClass(cls):\n        # Initiate the selenium webdriver\n        options = ChromeOptions()\n        options.add_argument('--headless')\n        cls.driver = Chrome(options=options)\n        cls.std_wait = WebDriverWait(cls.driver, 5)\n\n    @classmethod\n    def tearDownClass(cls):\n        # Quit the webdriver\n        cls.driver.quit()\n\n    ### Actual test methods\n\n    def test_foo_with_driver(self):\n        # Use self.driver here\n        # You also have access to self.server_url and self.app\n        # Example of using selenium to go to index page and try to find some elements (on a hypothetical app)\n        self.driver.get(self.server_url)\n        self.std_wait.until(EC.presence_of_element_located((By.LINK_TEXT, 'Register')))\n        self.std_wait.until(EC.presence_of_element_located((By.LINK_TEXT, 'Log In')))\n```\nThis is pretty straight forward, it's just a regular test case that you would use if you spawned the flask server from the terminal before running tests\n\nNow, you need to use the `LiveTestSuite` to run this. The previous testcases could be run using `unitttest.TestSuite`, or simply `unittest.main` but this *has to be* run using the custom suite\n```py\n# Assign the flask app here\napp = ...\n\n# Add TestFoo to suite\nsuite = flask_unittest.LiveTestSuite(app)\nsuite.addTest(unittest.makeSuite(TestFoo))\n\n# Run the suite\nrunner = unittest.TextTestRunner(verbosity=2)\nrunner.run(suite)\n```\nThe `LiveTestSuite` requires a built and configured `Flask` app object. It'll spawn this flask app using `app.run` as a daemon thread.\n\nBy default, the app runs on host 127.0.0.1 and port 5000. If you'd like to change this assign your custom host (as a `str`) and a port (as an `int`) to `app.config` under the key `HOST` and `PORT` respectively. (`app.config['HOST'] = '0.0.0.0'; app.config['PORT'] = 7000`)\n\nThe server is started when the suite is first run and it runs for the duration of the program\n\nYou will have access to the `app` passed to the suite inside `LiveTestCase`, using `self.app`. You will also have access to the url the server is running on inside the testcase, using `self.server_url`\n\n**Full Example** (of `LiveTestCase`): [`flask_live_test.py`](./tests/flask_live_test.py)\n**Full Example** (of `LiveTestSuite`): [`__init__.py`](./tests/__init__.py)\n\n# About request context and flask globals\nBoth `ClientTestCase` and `AppClientTestCase` allow you to use flask gloabls, such as `request`, `g`, and `session`, directly in your test method (and your `setUp` and `tearDown` methods)\n\nThis is because the `client` is *instantiated using a `with` block*, and the test method, the `setUp` and `tearDown` methods **happen inside the `with` block**\n\nVery rough psuedocode representation of this would look like-\n```py\nwith app.test_client() as client:\n    self.setUp(client)\n    self.test_method(client)\n    self.tearDown(client)\n```\nThis means you can treat everything in your test method, and `setUp` and `tearDown` methods, as if they are within a `with client:` block\n\nPractically, this lets you use the flask globals after making a request using `client` - which is great for testing\n\nAdditional info in the [official docs](https://flask.palletsprojects.com/en/1.1.x/testing/#keeping-the-context-around)\n\n# Emulating official flask testing example using `flask-unittest`\nThe official flask testing example can be found [in the flask repo](https://github.com/pallets/flask/tree/master/examples/tutorial/tests)\n\nThe original tests are written using `pytest`. This example demonstrates how `flask-unittest` can provide the same functionality for you, with the same degree of control!\n\nNote that this demonstration does not implement the `test_cli_runner` - since that is not directly supported by `flask-unittest` (yet). However, it's completely possible to simply use `.test_cli_runner()` on the `app` object in the testcases provided by `flask-unittest` to emulate this.\n\nThe primary thing to demonstrate here, is to emulate the pytest fixtures defined in the original [`conftest.py`](https://github.com/pallets/flask/blob/master/examples/tutorial/tests/conftest.py)-\n```py\n@pytest.fixture\ndef app():\n    \"\"\"Create and configure a new app instance for each test.\"\"\"\n    # create a temporary file to isolate the database for each test\n    db_fd, db_path = tempfile.mkstemp()\n    # create the app with common test config\n    app = create_app({\"TESTING\": True, \"DATABASE\": db_path})\n\n    # create the database and load test data\n    with app.app_context():\n        init_db()\n        get_db().executescript(_data_sql)\n\n    yield app\n\n    # close and remove the temporary database\n    os.close(db_fd)\n    os.unlink(db_path)\n\n\n@pytest.fixture\ndef client(app):\n    \"\"\"A test client for the app.\"\"\"\n    return app.test_client()\n```\n\nAs you can see, this creates the app **and** the test client *per test*. So we'll be using `AppClientTestCase` for this.\n\nLet's make a base class that provides functionality for this - all the other testcases can inherit from it. Defined in [`conftest.py`](./example/tests/conftest.py)\n\n```py\nimport flask_unittest\n\n\nclass TestBase(flask_unittest.AppClientTestCase):\n\n    def create_app(self):\n        \"\"\"Create and configure a new app instance for each test.\"\"\"\n        # create a temporary file to isolate the database for each test\n        db_fd, db_path = tempfile.mkstemp()\n        # create the app with common test config\n        app = create_app({\"TESTING\": True, \"DATABASE\": db_path})\n\n        # create the database and load test data\n        with app.app_context():\n            init_db()\n            get_db().executescript(_data_sql)\n\n            # Yield the app\n            '''\n            This can be outside the `with` block too, but we need to \n            call `close_db` before exiting current context\n            Otherwise windows will have trouble removing the temp file\n            that doesn't happen on unices though, which is nice\n            '''\n            yield app\n\n            ## Close the db\n            close_db()\n        \n        ## Cleanup temp file\n        os.close(db_fd)\n        os.remove(db_path)\n```\n\nThis is very similar to the original pytest fixtures and achieves the exact same functionality in the actual testcases too!\n\nDo note however, there's an extra call inside `with app.app_context()` - `close_db`. Windows struggles to remove the temp database using `os.remove` if it hasn't been closed already - so we have to do that (this is true for the original pytest methods too).\n\nAlso of note, creation of the `AuthActions` object should be handled manually in each of the test case. This is just how `unittest` works in contrast to `pytest`. This shouldn't pose any issue whatsoever though.\n\nNow let's look at an actual testcase. We'll be looking at `test_auth.py`, since it demonstrates the use of `app`, `client` and the flask globals very nicely.\n\nFor context, the original file is defined at [`test_auth.py`](https://github.com/pallets/flask/blob/master/examples/tutorial/tests/test_auth.py)\n\nThe full emulation of this file is at [`test_auth.py`](./example/tests/test_auth.py)\n\nOk! Let's look at the emulation of `test_register`.\n\nFor context, this is the original function-\n```py\ndef test_register(client, app):\n    # test that viewing the page renders without template errors\n    assert client.get(\"/auth/register\").status_code == 200\n\n    # test that successful registration redirects to the login page\n    response = client.post(\"/auth/register\", data={\"username\": \"a\", \"password\": \"a\"})\n    assert \"http://localhost/auth/login\" == response.headers[\"Location\"]\n\n    # test that the user was inserted into the database\n    with app.app_context():\n        assert (\n            get_db().execute(\"select * from user where username = 'a'\").fetchone()\n            is not None\n        )\n```\n\nAnd here's the `flask-unittest` version!\n```py\nfrom example.tests.conftest import AuthActions, TestBase\n\n\nclass TestAuth(TestBase):\n\n    def test_register(self, app, client):\n        # test that viewing the page renders without template errors\n        self.assertStatus(client.get(\"/auth/register\"), 200)\n\n        # test that successful registration redirects to the login page\n        response = client.post(\"/auth/register\", data={\"username\": \"a\", \"password\": \"a\"})\n        self.assertLocationHeader(response, \"http://localhost/auth/login\")\n\n        # test that the user was inserted into the database\n        with app.app_context():\n            self.assertIsNotNone(\n                get_db().execute(\"select * from user where username = 'a'\").fetchone()\n            )\n```\nSee how similar it is? The only difference is that the function should be a method in a class that is extending `flask_unittest.AppClientTestCase` with `create_app` defined. In our case, that's `TestBase` from `conftest.py` - so we extend from that.\n\nAs mentioned previously, each test method of an `AppClientTestCase` should have the parameters `self, app, client` - not necessarily with the same names but the second param **will be** the `Flask` object, and the third param **will be** the `FlaskClient` object\n\nAlso, this is using `self.assert...` functions as per `unittest` convention. However, regular `assert`s should work just fine.\n\nNice! Let's look at a function that uses flask globals - `test_login`\n\nHere's the original snippet-\n```py\ndef test_login(client, auth):\n    # test that viewing the page renders without template errors\n    assert client.get(\"/auth/login\").status_code == 200\n\n    # test that successful login redirects to the index page\n    response = auth.login()\n    assert response.headers[\"Location\"] == \"http://localhost/\"\n\n    # login request set the user_id in the session\n    # check that the user is loaded from the session\n    with client:\n        client.get(\"/\")\n        assert session[\"user_id\"] == 1\n        assert g.user[\"username\"] == \"test\"\n\n```\n\nAnd here's the `flask-unittest` version-\n```py\nclass TestAuth(TestBase):\n    \n    def test_login(self, _, client):\n        # test that viewing the page renders without template errors\n        self.assertStatus(client.get(\"/auth/login\"), 200)\n\n        # test that successful login redirects to the index page\n        auth = AuthActions(client)\n        response = auth.login()\n        self.assertLocationHeader(response, \"http://localhost/\")\n\n        # login request set the user_id in the session\n        # check that the user is loaded from the session\n        client.get(\"/\")\n        self.assertEqual(session[\"user_id\"], 1)\n        self.assertEqual(g.user[\"username\"], \"test\")\n```\n\n(this is a continuation of the previous example for `test_register`)\n\nOnce again, very similar. But there's a couple of things to note here.\n\nFirstly, notice we are ignoring the second argument of `test_login`, since we have no reason to use `app` here. We do, however, need to use the `FlaskClient` object\n\nAlso notice, we don't have to do `with client` to access the request context. `flask-unittest` already handles this for us, so we have direct access to `session` and `g`.\n\nLet's check out a case where we only use the `Flask` object and not the `FlaskClient` object - in which case, we can use `AppTestCase`.\n\nThe original function, `test_get_close_db`, is defined at [`test_db.py`](https://github.com/pallets/flask/blob/master/examples/tutorial/tests/test_db.py)\n\n```py\ndef test_get_close_db(app):\n    with app.app_context():\n        db = get_db()\n        assert db is get_db()\n\n    with pytest.raises(sqlite3.ProgrammingError) as e:\n        db.execute(\"SELECT 1\")\n\n    assert \"closed\" in str(e.value)\n```\n\nThe `flask-unittest` version can be seen at [`test_db.py`](./example/tests/test_db.py)\n\n```py\nimport flask_unittest\n\nclass TestDB(flask_unittest.AppTestCase):\n\n   # create_app omitted for brevity - remember to include it!\n   \n    def test_get_close_db(self, app):\n        with app.app_context():\n            db = get_db()\n            assert db is get_db()\n\n        try:\n            db.execute(\"SELECT 1\")\n        except sqlite3.ProgrammingError as e:\n            self.assertIn(\"closed\", str(e.args[0]))\n\n```\n\nVery similar once again!\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Unit testing flask applications made easy!",
    "version": "0.1.3",
    "project_urls": {
        "Homepage": "https://github.com/TotallyNotChase/flask-unittest"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "52cbd5217d8341c61081aaec931846e35ced62fbb6b67468e8e6c8db4ada69ec",
                "md5": "4798001fcd34d09c435cb3bbf46ba376",
                "sha256": "a847e1a56d2694040269c3b7fd6f7d67cb33615ae0549d32e3688d6276bda8bd"
            },
            "downloads": -1,
            "filename": "flask_unittest-0.1.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4798001fcd34d09c435cb3bbf46ba376",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 16119,
            "upload_time": "2022-06-05T06:45:10",
            "upload_time_iso_8601": "2022-06-05T06:45:10.422058Z",
            "url": "https://files.pythonhosted.org/packages/52/cb/d5217d8341c61081aaec931846e35ced62fbb6b67468e8e6c8db4ada69ec/flask_unittest-0.1.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ce658858ad436da94463af0c36bbbf9c42a893d0cfe0c788f6ab64dba739c86b",
                "md5": "162b8577e72d8231632d995360ee33e3",
                "sha256": "057470ddcfe188b28dae6ebfbda6006d88ca4c37d49e657de3533aa6eeba51d0"
            },
            "downloads": -1,
            "filename": "flask-unittest-0.1.3.tar.gz",
            "has_sig": false,
            "md5_digest": "162b8577e72d8231632d995360ee33e3",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 21648,
            "upload_time": "2022-06-05T06:45:12",
            "upload_time_iso_8601": "2022-06-05T06:45:12.517028Z",
            "url": "https://files.pythonhosted.org/packages/ce/65/8858ad436da94463af0c36bbbf9c42a893d0cfe0c788f6ab64dba739c86b/flask-unittest-0.1.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-06-05 06:45:12",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "TotallyNotChase",
    "github_project": "flask-unittest",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "flask-unittest"
}
        
Elapsed time: 0.26857s