Issue: Creating temporary databases for unit testing
Author: cLupus
State: closed
Created: 2018-02-01 08:52:01

Hi,
I am having trouble creating a database with tables that will be used in unit tests.
In 2.10.2, I used `test_database` (ref #1440 ), however now this functionality have been removed.

The desired functionality is to create / use a database, and create all the tables / (domain) models that is necessary.

Currently, I have been using a decorator;

```
from functools import wraps, partial


def use_database(func=None, database=None):
    if func is None:
        return partial(use_database, database=database)
    if database is None:
        database = get_database()

    @wraps(func)
    def use_database_decorator(*args, **kwargs):
        models = get_domain_models()
        with database:
            create_database(database, nuke=True)
            func(*args, **kwargs)
            database.drop_tables(models)
    return use_database_decorator


def create_database(database: Database, safe=True, nuke=False):
    if nuke:
        nuke_database(database)
    with database:
        database.bind(models)
        database.create_tables(
            get_domain_models(),
            safe=safe
        )


def nuke_database(database: Database):
    """ Removes everything in the database, except the file itself """
    models = get_domain_models()[::-1]
    database.drop_tables(models, safe=True)
```

Here, `get_domain_models()` returns a list of all the models / tables defined in the app / domain, except `BaseModel`, with _all_ models inherit.

`get_database()` returns an instance of an (in-memory) APSWDatabase, with some pragma statements set.

In some cases, there are no tables defined in the database, even though they have been explicitly created. An example of this is in the test bellow:

```
db = get_database()


@use_database(database=db)
def test_creation_of_database():
    created_table_names = db.get_tables()
    given_table_names = [model._meta.name for model in get_domain_models()]
    assert set(created_table_names) == set(given_table_names)
```

 

So `database.bind(...)` is actually a context-manager. Hence why it is not working as you'd expect. In your `create_database` function you call database.bind(models), which instantiates a _BoundModelsContext context-manager, but since the context-manager is never entered, the binding does not take place.

To simplify your existing code, you can:

```python
def create_database(database: Database, safe=True, nuke=False):
    if nuke:
        nuke_database(database)
    with database:
        for model in models:
            # Bind db. Alternatively you could use model.bind(database) but, without specifying
            # any arguments, that will traverse the model graph binding related models, which
            # would be redundant.
            model._meta.set_database(database)
            # Alt version: model.bind(database, bind_refs=False, bind_backrefs=False)
        database.create_tables(
            get_domain_models(),
            safe=safe
        )
```

For individual test-cases, I use a decorator sometimes in the tests, `requires_models`, which looks like this:

```python
def requires_models(*models):
    def decorator(method):
        @wraps(method)
        def inner(self):
            _db_mapping = {}
            for model in models:
                _db_mapping[model] = model._meta.database
                model._meta.set_database(self.database)
            self.database.drop_tables(models, safe=True)
            self.database.create_tables(models)

            try:
                method(self)
            finally:
                try:
                    self.database.drop_tables(models)
                except:
                    pass
                for model in models:
                    model._meta.set_database(_db_mapping[model])
        return inner
    return decorator
```

Here's a simple version that should theoretically work:

```python
            MODELS = (User, Account, Note)

            # Bind the given models to the db for the duration of wrapped block.
            def use_test_database(fn):
                @wraps(fn)
                def inner(self):
                    with test_db.bind(MODELS):
                        test_db.create_tables(MODELS)
                        try:
                            fn(self)
                        finally:
                            test_db.drop_tables(MODELS)
                return inner


            class TestSomething(TestCase):
                @use_test_database
                def test_something(self):
                    # ... models are bound to test database ...
                    pass
```

Here's a simple solution for a class decorator so you don't need to add it to every method.

This has the advantage of allowing the with_test_db class decorator to take a param (MODELS); this is set as an instance variable in the constructor.  __ call __ then wraps the unittest class, replaces the testunit run() method with a new run method which has the new memory Sqlite code.

app/model.py
```
class BaseModel(peewee.Model):
    class Meta:
        database = db

class SomeTable(BaseModel):
    ...

MODELS = (SomeTable,)
```

testutils.py
```
class with_test_db:
    def __init__(self, dbs: tuple):
        self.dbs = dbs

    def __call__(self, cls):
        def run(innerself, *args, **kwargs):
            test_db = playhouse.sqlite_ext.SqliteExtDatabase(":memory:")
            with test_db.bind_ctx(self.dbs):
                test_db.create_tables(self.dbs)
                try:
                    cls.run(innerself, *args, **kwargs)
                finally:
                    test_db.drop_tables(self.dbs)
                    test_db.close()

        return type(cls.__name__, (cls,), {"run": run})


@testutils.with_test_db(app.MODELS)
class MyAmazingTest(unittest.TestCase):
    def test_something(self):
        ...

    def test_something_else(self):
        ...
```

Voila. Only one decorator needed per class which all tests can use. 

Thanks to https://www.dvlv.co.uk/a-super-helpful-decorator-for-peeweeflask-unit-testing.html for the inspiration.