Skip to content

Commit

Permalink
Merge pull request #1660 from hackebrot/parametrize-with-fixtures
Browse files Browse the repository at this point in the history
Proposal: Parametrize with fixtures
  • Loading branch information
The-Compiler authored Jun 25, 2016
2 parents f2bb3df + 1b6bc4d commit 35cd12e
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@
Thanks `@Vogtinator`_ for reporting. Thanks to `@RedBeardCode`_ and
`@tomviner`_ for PR.

* Add proposal to docs for a new feature that enables users to combine multiple
fixtures into one. Thanks to `@hpk42`_ and `@hackebrot`_.

*

.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
Expand Down
148 changes: 148 additions & 0 deletions doc/en/proposals/parametrize_with_fixtures.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
=========================
Parametrize with fixtures
=========================

Problem
-------

As a user I have functional tests that I would like to run against various
scenarios.

In this particular example we want to generate a new project based on a
cookiecutter template. We want to test default values but also data that
emulates user input.

- use default values

- emulate user input

- specify 'author'

- specify 'project_slug'

- specify 'author' and 'project_slug'

This is how a functional test could look like:

.. code-block:: python
import pytest
@pytest.fixture
def default_context():
return {'extra_context': {}}
@pytest.fixture(params=[
{'author': 'alice'},
{'project_slug': 'helloworld'},
{'author': 'bob', 'project_slug': 'foobar'},
])
def extra_context(request):
return {'extra_context': request.param}
@pytest.fixture(params=['default', 'extra'])
def context(request):
if request.param == 'default':
return request.getfuncargvalue('default_context')
else:
return request.getfuncargvalue('extra_context')
def test_generate_project(cookies, context):
"""Call the cookiecutter API to generate a new project from a
template.
"""
result = cookies.bake(extra_context=context)
assert result.exit_code == 0
assert result.exception is None
assert result.project.isdir()
Issues
------

* By using ``request.getfuncargvalue()`` we rely on actual fixture function
execution to know what fixtures are involved, due to it's dynamic nature
* More importantly, ``request.getfuncargvalue()`` cannot be combined with
parametrized fixtures, such as ``extra_context``
* This is very inconvenient if you wish to extend an existing test suite by
certain parameters for fixtures that are already used by tests

pytest version 3.0 reports an error if you try to run above code::

Failed: The requested fixture has no parameter defined for the current
test.

Requested fixture 'extra_context'


Proposed solution
-----------------

A new function that can be used in modules can be used to dynamically define
fixtures from existing ones.

.. code-block:: python
pytest.define_combined_fixture(
name='context',
fixtures=['default_context', 'extra_context'],
)
The new fixture ``context`` inherits the scope from the used fixtures and yield
the following values.

- ``{}``

- ``{'author': 'alice'}``

- ``{'project_slug': 'helloworld'}``

- ``{'author': 'bob', 'project_slug': 'foobar'}``

Alternative approach
--------------------

A new helper function named ``fixture_request`` tells pytest to yield all
parameters of a fixture.

.. code-block:: python
@pytest.fixture(params=[
pytest.fixture_request('default_context'),
pytest.fixture_request('extra_context'),
])
def context(request):
"""Returns all values for ``default_context``, one-by-one before it
does the same for ``extra_context``.
request.param:
- {}
- {'author': 'alice'}
- {'project_slug': 'helloworld'}
- {'author': 'bob', 'project_slug': 'foobar'}
"""
return request.param
The same helper can be used in combination with ``pytest.mark.parametrize``.

.. code-block:: python
@pytest.mark.parametrize(
'context, expected_response_code',
[
(pytest.fixture_request('default_context'), 0),
(pytest.fixture_request('extra_context'), 0),
],
)
def test_generate_project(cookies, context, exit_code):
"""Call the cookiecutter API to generate a new project from a
template.
"""
result = cookies.bake(extra_context=context)
assert result.exit_code == exit_code

0 comments on commit 35cd12e

Please sign in to comment.