pytest-django-ifactory


Namepytest-django-ifactory JSON
Version 1.2.1 PyPI version JSON
download
home_page
SummaryA model instance factory for pytest-django
upload_time2023-08-27 18:58:44
maintainer
docs_urlNone
author
requires_python>=3.8
licenseBSD-3-Clause
keywords pytest django database testing
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pytest-django-ifactory

A model instance factory for [pytest-django][].

[pytest-django]: https://pytest-django.readthedocs.io/

## Motivation

pytest-django-ifactory makes it easy to create model instances for
your test cases even if they contain a lot of non-nullable fields and
complex foreign key relationships. If you every felt like you spent
too much time coming up with dummy values for you models' fields just
to get an instance into the database to use in a test then
pytest-django-ifactory might be for you.

pytest's fixtures are great and perfect if you want to have a static
model instance available in the database for your tests. Problems
arise however when you want to vary one of its fields. To reuse your
fixture in that case you need to modify the field and save the
instance back to the database (assuming your test needs it in the
database, of course). This results in at least two lines of extra
setup code in you test case and an extra call to the database.

pytest-django-ifactory is simply an instance factory (hence
*ifactory*) function that automatically detects your Django models and
tries to come up with acceptable defaults for the fields you don't
care about. This includes generating unique values for fields marked
as unique and to create related instances for non-nullable foreign
keys.

## Usage

This plugin comes with two fixtures: `ifactory` and
`transactional_ifactory`.  Use them when you need to put model
instances in the database.  `ifactory` and `transactional_ifactory`
are identical except that the latter uses [pytest-django][]'s
`transactional_db` fixture instead of the `db` fixture.  See
pytest-django's and Django's documentation for when you would want to
use it.  Below is a contrived example to test a function that finds
duplicate names of your users:

```python
from itertools import groupby
from operator import methodcaller

from django.contrib.auth.models import User


def get_duplicate_names():
    all_users = User.objects.order_by("last_name", "first_name")
    users_by_name = groupby(all_users, methodcaller("get_full_name"))
    return [full_name for full_name, users in users_by_name if len(list(users)) > 1]


def test_get_duplicate_names(ifactory):
    ifactory.auth.user(first_name="Albert", last_name="Einstein")
    ifactory.auth.user(first_name="Albert", last_name="Einstein")
    ifactory.auth.user(first_name="Erwin", last_name="Schrodinger")
    ifactory.auth.user(first_name="Richard", last_name="Feynman")
    assert get_duplicate_names() == ["Albert Einstein"]
    assert User.objects.count() == 4
```

You find you models namespaced by the application name and the model
name on the `ifactory` instance. For instance, if you have created a
`Book` model in a `library` application (and put your library
application in INSTALLED_APPS), its factory function will be
`ifactory.library.book()`. This function accepts the same arguments as
your model constructor does and returns the created instance.

Notice in the example above that we create four new users without
specifying their unique usernames. The goal of this project is that
you should never have to specify the value of a field if you're not
interested in that value in your test. Conversely, you should never
rely on a value you haven't explicity set. This library gives no
guarantees that the value it chooses for a field will be the same the
next time the test is run. It just tries to make sure that the
instance can be saved to the database without any integrity errors.

If you're running a type checker on your code, prefer the `create()`
method on `ifactory` to get the correct return type:

```python
ifactory.create(User, first_name="Albert", last_name="Einstein")
```

While I would recommend against it, if you do want to know which
default value will be used, you can use the
`pytest_django_ifactory_configure` hook. A good place to put it is in
your *conftest.py*:

```python
def pytest_django_ifactory_configure(ifactory):
    ifactory.configure_defaults(
        "auth.user", first_name="Albert", last_name="Einstein"
    )
```

From now on, all users in your tests will be Albert Einstein unless
you say otherwise:

```python
def test_albert_by_default(ifactory):
    albert = ifactory.auth.user()
    assert albert.get_full_name() == "Albert Einstein"
    not_albert = ifactory.auth.user(first_name="Erwin", last_name="Schrodinger")
    assert not_albert.full_name() == "Erwin Schrodinger"
```

While the above example might not be the best idea as your test suit
grows the hook can be used to enforce constraints in your models that
this library is unaware of, e.g., validation errors raised by your
models that depend on the model's field values.

## Development

This project uses [black][] to auto-format the code, [ruff][] to lint
it, [mypy][] to type check it, [pytest][] to test it, and finally
[check-manifest][] to check the project's
[MANIFEST.in](MANIFEST.in). To facilitate (and remember) to actually
run all these tools, [pre-commit][] is used. Hence, the only hard
development requirement is *pre-commit*. Install it and run

```console
$ pre-commit install
```

once the first time you check out the repo and from now on all checks
except the type and unit tests will be run everytime a commit is
made. The type checking is run manually with mypy,

```console
$ mypy .
```

and the unit tests are run with pytest

```console
$ pytest
```

Gitlab CI is configured to run the tests with Python 3.8-3.11 and all
supported Django LTS versions.

If you want to install all development requirements to run them
manually (and without using `pre-commit run --all-files`), use the
optional `dev` requirement group:

```console
$ python -m pip install -e .[dev]
```

[black]: https://github.com/ambv/black
[check-manifest]: https://github.com/mgedmin/check-manifest
[mypy]: https://www.mypy-lang.org/
[pre-commit]: https://github.com/pre-commit/pre-commit
[pytest]: http://pytest.org/
[ruff]: https://beta.ruff.rs/docs

## License

Like [pytest-django][], pytest-django-ifactory is released under the
[BSD 3-clause](LICENSE) license.

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "pytest-django-ifactory",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "pytest,django,database,testing",
    "author": "",
    "author_email": "Mattias Jakobsson <mjakob422@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/df/f4/b5b064167159e3c3021655902c3ece9d83d4c5b4f6f76d9184833c9a4e96/pytest-django-ifactory-1.2.1.tar.gz",
    "platform": null,
    "description": "# pytest-django-ifactory\n\nA model instance factory for [pytest-django][].\n\n[pytest-django]: https://pytest-django.readthedocs.io/\n\n## Motivation\n\npytest-django-ifactory makes it easy to create model instances for\nyour test cases even if they contain a lot of non-nullable fields and\ncomplex foreign key relationships. If you every felt like you spent\ntoo much time coming up with dummy values for you models' fields just\nto get an instance into the database to use in a test then\npytest-django-ifactory might be for you.\n\npytest's fixtures are great and perfect if you want to have a static\nmodel instance available in the database for your tests. Problems\narise however when you want to vary one of its fields. To reuse your\nfixture in that case you need to modify the field and save the\ninstance back to the database (assuming your test needs it in the\ndatabase, of course). This results in at least two lines of extra\nsetup code in you test case and an extra call to the database.\n\npytest-django-ifactory is simply an instance factory (hence\n*ifactory*) function that automatically detects your Django models and\ntries to come up with acceptable defaults for the fields you don't\ncare about. This includes generating unique values for fields marked\nas unique and to create related instances for non-nullable foreign\nkeys.\n\n## Usage\n\nThis plugin comes with two fixtures: `ifactory` and\n`transactional_ifactory`.  Use them when you need to put model\ninstances in the database.  `ifactory` and `transactional_ifactory`\nare identical except that the latter uses [pytest-django][]'s\n`transactional_db` fixture instead of the `db` fixture.  See\npytest-django's and Django's documentation for when you would want to\nuse it.  Below is a contrived example to test a function that finds\nduplicate names of your users:\n\n```python\nfrom itertools import groupby\nfrom operator import methodcaller\n\nfrom django.contrib.auth.models import User\n\n\ndef get_duplicate_names():\n    all_users = User.objects.order_by(\"last_name\", \"first_name\")\n    users_by_name = groupby(all_users, methodcaller(\"get_full_name\"))\n    return [full_name for full_name, users in users_by_name if len(list(users)) > 1]\n\n\ndef test_get_duplicate_names(ifactory):\n    ifactory.auth.user(first_name=\"Albert\", last_name=\"Einstein\")\n    ifactory.auth.user(first_name=\"Albert\", last_name=\"Einstein\")\n    ifactory.auth.user(first_name=\"Erwin\", last_name=\"Schrodinger\")\n    ifactory.auth.user(first_name=\"Richard\", last_name=\"Feynman\")\n    assert get_duplicate_names() == [\"Albert Einstein\"]\n    assert User.objects.count() == 4\n```\n\nYou find you models namespaced by the application name and the model\nname on the `ifactory` instance. For instance, if you have created a\n`Book` model in a `library` application (and put your library\napplication in INSTALLED_APPS), its factory function will be\n`ifactory.library.book()`. This function accepts the same arguments as\nyour model constructor does and returns the created instance.\n\nNotice in the example above that we create four new users without\nspecifying their unique usernames. The goal of this project is that\nyou should never have to specify the value of a field if you're not\ninterested in that value in your test. Conversely, you should never\nrely on a value you haven't explicity set. This library gives no\nguarantees that the value it chooses for a field will be the same the\nnext time the test is run. It just tries to make sure that the\ninstance can be saved to the database without any integrity errors.\n\nIf you're running a type checker on your code, prefer the `create()`\nmethod on `ifactory` to get the correct return type:\n\n```python\nifactory.create(User, first_name=\"Albert\", last_name=\"Einstein\")\n```\n\nWhile I would recommend against it, if you do want to know which\ndefault value will be used, you can use the\n`pytest_django_ifactory_configure` hook. A good place to put it is in\nyour *conftest.py*:\n\n```python\ndef pytest_django_ifactory_configure(ifactory):\n    ifactory.configure_defaults(\n        \"auth.user\", first_name=\"Albert\", last_name=\"Einstein\"\n    )\n```\n\nFrom now on, all users in your tests will be Albert Einstein unless\nyou say otherwise:\n\n```python\ndef test_albert_by_default(ifactory):\n    albert = ifactory.auth.user()\n    assert albert.get_full_name() == \"Albert Einstein\"\n    not_albert = ifactory.auth.user(first_name=\"Erwin\", last_name=\"Schrodinger\")\n    assert not_albert.full_name() == \"Erwin Schrodinger\"\n```\n\nWhile the above example might not be the best idea as your test suit\ngrows the hook can be used to enforce constraints in your models that\nthis library is unaware of, e.g., validation errors raised by your\nmodels that depend on the model's field values.\n\n## Development\n\nThis project uses [black][] to auto-format the code, [ruff][] to lint\nit, [mypy][] to type check it, [pytest][] to test it, and finally\n[check-manifest][] to check the project's\n[MANIFEST.in](MANIFEST.in). To facilitate (and remember) to actually\nrun all these tools, [pre-commit][] is used. Hence, the only hard\ndevelopment requirement is *pre-commit*. Install it and run\n\n```console\n$ pre-commit install\n```\n\nonce the first time you check out the repo and from now on all checks\nexcept the type and unit tests will be run everytime a commit is\nmade. The type checking is run manually with mypy,\n\n```console\n$ mypy .\n```\n\nand the unit tests are run with pytest\n\n```console\n$ pytest\n```\n\nGitlab CI is configured to run the tests with Python 3.8-3.11 and all\nsupported Django LTS versions.\n\nIf you want to install all development requirements to run them\nmanually (and without using `pre-commit run --all-files`), use the\noptional `dev` requirement group:\n\n```console\n$ python -m pip install -e .[dev]\n```\n\n[black]: https://github.com/ambv/black\n[check-manifest]: https://github.com/mgedmin/check-manifest\n[mypy]: https://www.mypy-lang.org/\n[pre-commit]: https://github.com/pre-commit/pre-commit\n[pytest]: http://pytest.org/\n[ruff]: https://beta.ruff.rs/docs\n\n## License\n\nLike [pytest-django][], pytest-django-ifactory is released under the\n[BSD 3-clause](LICENSE) license.\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": "A model instance factory for pytest-django",
    "version": "1.2.1",
    "project_urls": {
        "Homepage": "https://gitlab.com/gorilladev/pytest-django-ifactory"
    },
    "split_keywords": [
        "pytest",
        "django",
        "database",
        "testing"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "748ce93d25f46c734313bd361812662e439374d0a152919432710165ce0baae7",
                "md5": "3d8a82b20aa4f40431771511fb6ceef1",
                "sha256": "4897991ce6bd9e122f577fecc9cfbf153e2c2e8a0d9d4e65473e02cdce6ee380"
            },
            "downloads": -1,
            "filename": "pytest_django_ifactory-1.2.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3d8a82b20aa4f40431771511fb6ceef1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 11656,
            "upload_time": "2023-08-27T18:58:43",
            "upload_time_iso_8601": "2023-08-27T18:58:43.088532Z",
            "url": "https://files.pythonhosted.org/packages/74/8c/e93d25f46c734313bd361812662e439374d0a152919432710165ce0baae7/pytest_django_ifactory-1.2.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dff4b5b064167159e3c3021655902c3ece9d83d4c5b4f6f76d9184833c9a4e96",
                "md5": "5e113d76f9780f57a8b7ae3a064dc8de",
                "sha256": "4219f12053dfd17b358950dc10aded6ef5fbd37b51301c0dd2e731440da07bfc"
            },
            "downloads": -1,
            "filename": "pytest-django-ifactory-1.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "5e113d76f9780f57a8b7ae3a064dc8de",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 20057,
            "upload_time": "2023-08-27T18:58:44",
            "upload_time_iso_8601": "2023-08-27T18:58:44.567639Z",
            "url": "https://files.pythonhosted.org/packages/df/f4/b5b064167159e3c3021655902c3ece9d83d4c5b4f6f76d9184833c9a4e96/pytest-django-ifactory-1.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-08-27 18:58:44",
    "github": false,
    "gitlab": true,
    "bitbucket": false,
    "codeberg": false,
    "gitlab_user": "gorilladev",
    "gitlab_project": "pytest-django-ifactory",
    "lcname": "pytest-django-ifactory"
}
        
Elapsed time: 0.11742s