Skip to content

Testing

Testing your code is a hated but good practice. Repository ORM tries to make your testing experience less cumbersome.

You can use different strategies depending on the level of testing. For unit and integration tests the FakeRepository may be your best option, for end-to-end ones, I'd use TinyDBRepository.

Unit and integration tests

Unit tests are meant to test individual units of code, for example, a function or a method of a class. You'll probably use them to test your models or services.

import pytest

from repository_orm import Entity, FakeRepository, Repository


@pytest.fixture()
def repo() -> FakeRepository:
    return FakeRepository()


class Author(Entity):
    first_name: str


def create_greeting(repo: Repository, author_id: int) -> str:
    author = repo.get(author_id, Author)
    return f"Hi {author.first_name}!"


def test_greetings(repo: FakeRepository) -> None:
    author = Author(id_=20, first_name="Brandon")
    repo.add(author)
    repo.commit()

    result = create_greeting(repo, 20)

    assert result == "Hi Brandon!" # noqa

End-to-end tests

End-to-end tests evaluate the whole functionality of the program from the eyes of the user. For example, testing a command line or the API endpoint. Usually the program loads the repository from storage at start time, which means that the FakeRepository can't be used.

We're going to create a click command line program called greet that once it's called, it will return the first author in the repository. It's a little bit more complex but bare with me.

from pathlib import Path
from typing import Generator

import click
import pytest
from click.testing import CliRunner

from repository_orm import Entity, Repository, TinyDBRepository, load_repository


# Model
class Author(Entity):
    first_name: str


# Fixtures
@pytest.fixture(name="db_tinydb")
def db_tinydb_(tmp_path: Path) -> str:
    tinydb_file_path = str(tmp_path / "tinydb.db")
    return f"tinydb:///{tinydb_file_path}"


@pytest.fixture()
def repo(db_tinydb: str) -> Generator[TinyDBRepository, None, None]:
    repo = TinyDBRepository(database_url=db_tinydb)

    yield repo

    repo.close()


# Service
def create_greeting(repo: Repository) -> str:
    first_author = repo.all(Author)[0]
    return f"Hi {first_author.first_name}, you're the first author!"


# Entrypoint
@click.command()
@click.argument("database_url")
def greet(database_url: str) -> None:
    repo = load_repository(database_url)

    print(create_greeting(repo))

    repo.close()


# Test
def test_greetings(repo: TinyDBRepository, db_tinydb: str) -> None:
    author = Author(id_=20, first_name="Brandon")
    repo.add(author)
    repo.commit()
    runner = CliRunner()

    result = runner.invoke(greet, [db_tinydb])

    assert result.exit_code == 0
    assert result.output == "Hi Brandon, you're the first author!\n" # noqa

First we define the fixtures, we start with db_tinydb that uses the pytest's tmp_path fixture to create a random temporal directory and then sets the database url. The repo fixture uses that database url to create a TinyDBRepository instance.

The model Author and service create_greeting are similar to the previous section.

The entrypoint is where we define the command line interface, in this case the command is going to be called greet and it's going to accept an argument called database_url, it will initialize the repository and use the create_greeting to show the message to the user through the terminal.

To test this code, we first need to add an Author, so the function can look for it. We do it in the first three lines of test_greetings. Then we initialize the runner which simulates a command line call, and we make sure that the program exited well, and gave the output we expected.


Last update: 2022-11-25