Skip to content

Let the pytest fixtures use a custom user factory - #259

Open
jefftriplett wants to merge 5 commits into
mainfrom
tp-user-factory
Open

jefftriplett wants to merge 5 commits into
mainfrom
tp-user-factory

Conversation

@jefftriplett

Copy link
Copy Markdown
Member

Closes #125, open since 2020.

The gap

TestCase subclasses can set a user_factory attribute and make_user() will build users through it instead of User.objects.create_user(). The tp and tp_api fixtures have no class of their own to set it on, so pytest users could never reach that hook. If your User model has required fields or no username field, tp.make_user() simply could not build one.

The option

[tool.pytest.ini_options]
test_plus_user_factory = "myapp.factories.UserFactory"

A dotted path to the factory. Unset, which is the default, nothing changes: make_user() calls User.objects.create_user() exactly as before. Bad values raise pytest.UsageError naming the problem rather than leaking an ImportError or AttributeError out of a fixture.

The part that was not obvious

Assigning t.user_factory = factory on the fixture instance looks right and does nothing. make_user() is a classmethod and reads cls.user_factory, so an instance attribute never reaches it. The fixtures build a small subclass instead:

cls = TestCase if factory is None else type("TestCase", (TestCase,), {"user_factory": factory})

My first attempt did the instance assignment. The option loaded correctly, tp.user_factory was set, and users were still created by create_user(). The end to end test is what caught it, which is why it is written the way it is rather than asserting on the attribute alone.

Backwards compatibility

Purely additive. The option defaults to unset, TestCase.user_factory still defaults to None, and the 105 pre-existing tests are untouched and passing.

Tests

Nine added, covering the loader and the real behaviour:

  • unset and empty string both resolve to None
  • a valid dotted path resolves to the factory object
  • a path with no module, an unimportable module, and a missing attribute each raise pytest.UsageError with a useful message
  • tp.user_factory is None by default, and make_user() still works without a factory
  • end to end: pytest runs in a subprocess with -o test_plus_user_factory=... and asserts the created user's email comes from the factory's sequence, which is the field that proves the factory ran

114 passed, 1 skipped, 2 xfailed, up from 105. Lint clean, docs build clean.

Docs

docs/usage.md gains a "A custom user factory" section under pytest usage, showing both the pyproject.toml and pytest.ini forms and saying when you would want it.

Closes #125.

TestCase subclasses can set user_factory so make_user() builds users
through a factory instead of User.objects.create_user(). The tp and
tp_api fixtures had no class of their own to set it on, so anyone whose
User model needs a factory, because it has required fields or no
username field, could not use tp.make_user() at all.

Adds a test_plus_user_factory ini option holding a dotted path. Unset,
which is the default, everything behaves exactly as before.

The fixtures build a subclass rather than assigning to the instance:
make_user() is a classmethod and reads cls.user_factory, so an instance
attribute never reaches it. That was not obvious and cost a debugging
round, hence the comment in the code.

Bad values fail as pytest.UsageError with the reason, rather than an
ImportError or AttributeError from inside a fixture.
Ruff's PLW1510. The test asserts on returncode itself so it can report
the subprocess output on failure.
The reference rendered test_plus.test, status_codes, and runner, but not
plugin, so tp, tp_api, and api_client had no entry at all. Adds the
module, and docstrings for the three fixtures, which had none.
# Conflicts:
#	CHANGELOG.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Set custom UserFactory for tp pytest fixture

1 participant