summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/pytest/doc/en/yieldfixture.rst
blob: ee88a27df597d1c0da3177043a00add4ae6d3eae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
.. _yieldfixture:

Fixture functions using "yield" / context manager integration
---------------------------------------------------------------

.. versionadded:: 2.4

.. regendoc:wipe

pytest-2.4 allows fixture functions to seamlessly use a ``yield`` instead 
of a ``return`` statement to provide a fixture value while otherwise
fully supporting all other fixture features.

Let's look at a simple standalone-example using the ``yield`` syntax::

    # content of test_yield.py
    
    import pytest

    @pytest.yield_fixture
    def passwd():
        print ("\nsetup before yield")
        f = open("/etc/passwd")
        yield f.readlines()
        print ("teardown after yield")
        f.close()

    def test_has_lines(passwd):
        print ("test called")
        assert passwd

In contrast to :ref:`finalization through registering callbacks
<finalization>`, our fixture function used a ``yield``
statement to provide the lines of the ``/etc/passwd`` file.  
The code after the ``yield`` statement serves as the teardown code, 
avoiding the indirection of registering a teardown callback function.   

Let's run it with output capturing disabled::

    $ py.test -q -s test_yield.py
    
    setup before yield
    test called
    .teardown after yield
    
    1 passed in 0.12 seconds

We can also seamlessly use the new syntax with ``with`` statements.
Let's simplify the above ``passwd`` fixture::

    # content of test_yield2.py
    
    import pytest

    @pytest.yield_fixture
    def passwd():
        with open("/etc/passwd") as f:
            yield f.readlines()

    def test_has_lines(passwd):
        assert len(passwd) >= 1

The file ``f`` will be closed after the test finished execution
because the Python ``file`` object supports finalization when
the ``with`` statement ends. 

Note that the yield fixture form supports all other fixture
features such as ``scope``, ``params``, etc., thus changing existing
fixture functions to use ``yield`` is straightforward.

.. note::

    While the ``yield`` syntax is similar to what
    :py:func:`contextlib.contextmanager` decorated functions
    provide, with pytest fixture functions the part after the
    "yield" will always be invoked, independently from the
    exception status of the test function which uses the fixture.
    This behaviour makes sense if you consider that many different
    test functions might use a module or session scoped fixture.


Discussion and future considerations / feedback
++++++++++++++++++++++++++++++++++++++++++++++++++++

There are some topics that are worth mentioning:

- usually ``yield`` is used for producing multiple values.
  But fixture functions can only yield exactly one value.
  Yielding a second fixture value will get you an error.
  It's possible we can evolve pytest to allow for producing
  multiple values as an alternative to current parametrization.
  For now, you can just use the normal
  :ref:`fixture parametrization <fixture-parametrize>`
  mechanisms together with ``yield``-style fixtures.

- lastly ``yield`` introduces more than one way to write
  fixture functions, so what's the obvious way to a newcomer?

If you want to feedback or participate in discussion of the above
topics, please join our :ref:`contact channels`, you are most welcome.