question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUESTION] How can I populate data in a test database using the yield session functionality with pytest?

See original GitHub issue

Description

How can I populate data in a test database dynamically using the yield session functionality with pytest? What are best practices in FastAPI testing? Is there a good example I could follow?

Additional context

I am getting this error: AttributeError: 'Depends' object has no attribute 'add'

I have 2 files as an example

test_apps.py

import pytest
from fastapi import Depends

from app.main import app
from config import Config
from data_init import APP
from models.models import AppModel
from test_base import client, get_test_db
from sqlalchemy.orm import Session

@pytest.fixture(scope="session", autouse=True)
def init(db: Session=Depends(get_test_db)):
    new_app = AppModel(**APP)    
    db.add(new_app)
    db.commit()
    db.refresh(new_app)
    AppModel.filter_or_404(db, id=APP["id"])

def test_get_apps(client, db: Session=Depends(get_test_db)):
    response = client.get("/applications")
    assert response.status_code == 200

test_base.py

from typing import Optional, AsyncIterable

import pytest
from fastapi import Depends
from starlette.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine as Database
from sqlalchemy.orm import Session
from sqlalchemy_utils import database_exists, create_database, drop_database

from app.main import app
from app.dependency import get_db
from models.__base import Base
from config import Config

url = str(Config.SQLALCHEMY_DATABASE_URI + "_test")
_db_conn = create_engine(url)


def get_test_db_conn() -> Database:
    assert _db_conn is not None
    return _db_conn


def get_test_db(db_conn=Depends(get_test_db_conn)) -> AsyncIterable[Session]:
    sess = Session(bind=db_conn)

    try:
        yield sess
    finally:
        sess.close()


@pytest.fixture(scope="session", autouse=True)
def create_test_database():
    """
  Create a clean database on every test case.
  For safety, we should abort if a database already exists.

  We use the `sqlalchemy_utils` package here for a few helpers in consistently
  creating and dropping the database.
  """
    try:
        assert not database_exists(url), "Test database already exists. Aborting tests."
        create_database(url)  # Create the test database.
        Base.metadata.create_all(_db_conn)  # Create the tables.
        app.dependency_overrides[get_db] = get_test_db # Mock the Dependency
        yield  # Run the tests.
    finally:
        drop_database(url)  # Drop the test database.


@pytest.fixture()
def client():
    """
    When using the 'client' fixture in test cases, we'll get full database
    rollbacks between test cases:
    """
    with TestClient(app) as client:
        yield client

Is there a better way or a better practice to handle testing?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

9reactions
avocraftcommented, Jan 8, 2020

Thank you for pointing me to the right direction @dmontagu

I got it working like this:

test_base.py

url = str(Config.SQLALCHEMY_DATABASE_URI + "_test")
_db_conn = create_engine(url)

def get_test_db_conn() -> Database:
    assert _db_conn is not None
    return _db_conn


def get_test_db() -> AsyncIterable[Session]:
    sess = Session(bind=_db_conn)

    try:
        yield sess
    finally:
        sess.close()


@pytest.fixture(scope="session", autouse=True)
def create_test_database():
    """
  Create a clean database on every test case.

  We use the `sqlalchemy_utils` package here for a few helpers in consistently
  creating and dropping the database.
  """
    if database_exists(url):
        drop_database(url)
    create_database(url)  # Create the test database.
    Base.metadata.create_all(_db_conn)  # Create the tables.
    app.dependency_overrides[get_db] = get_test_db  # Mock the Database Dependency
    yield  # Run the tests.
    drop_database(url)  # Drop the test database.


@pytest.yield_fixture
def test_db_session():
    """Returns an sqlalchemy session, and after the test tears down everything properly."""

    session = Session(bind=_db_conn)

    yield session
    # Drop all data after each test
    for tbl in reversed(Base.metadata.sorted_tables):
        _db_conn.execute(tbl.delete())
    # put back the connection to the connection pool
    session.close()


@pytest.fixture()
def client():
    """
    When using the 'client' fixture in test cases, we'll get full database
    rollbacks between test cases:
    """
    with TestClient(app) as client:
        yield client

test_app.py

# Required imports 'create_test_database'
from test_base import (
    client,
    get_test_db,
    create_test_database,
    url,
    test_db_session as db,
)


class TestApps:
    def setup(self):
        self.application_url = "/applications"

    @pytest.fixture(autouse=True)
    def setup_db_data(self, db):
        """Set up all the data before each test"""
        new_app = AppModel(**APP)
        db.add(new_app)
        db.commit()
        db.refresh(new_app)

    def test_404(self, client, db):
        response = client.get("/applications")
        assert response.status_code == 200

I am a little confused why create_test_database needs to be imported but never used anywhere. But that will do for now. Thanks !

3reactions
dmontagucommented, Jan 8, 2020

You need to import it otherwise pytest won’t know it exists, and won’t be able to find it while looking for autouse fixtures.

If you place the autouse fixture in a file called conftest.py that is in the same folder or a parent folder, it will be automatically detected and imported by pytest (conftest.py is like pytest’s version of __init__.py). Typically it makes sense to put any autouse=True fixtures in a conftest.py to make sure they get used without needing to import anyting.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Populate your Django test database with pytest fixtures - FlowFX
I'm working on a side project that uses data from an external API. For performance reasons I store this data in a local...
Read more >
How to set up and tear down a database between tests in ...
For each test that has test_db in its argument list pytest first runs Base.metadata.create_all(bind=engine) , then yields to the test code, ...
Read more >
Effective Python Testing With Pytest
In this tutorial, you'll learn how to take your testing to the next level with pytest. You'll cover intermediate and advanced pytest ......
Read more >
Fun with Fixtures for Database Applications | by Geoffrey Koh
We use one fixture to setup the database, and another to insert the test data. Within the core functions, it is a good...
Read more >
pytest fixtures: explicit, modular, scalable
These are accessed by test functions through arguments; for each fixture ... to re-use fixtures across function, class, module or whole test session...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found