Returning multiple objects from a pytest fixture
Question:
I am learning how to use pytest by testing a simple event emitter implementation.
Basically, it looks like this
class EventEmitter():
def __init__(self):
...
def subscribe(self, event_map):
# adds listeners to provided in event_map events
def emit(self, event, *args):
# emits event with given args
For convenience, I created a Listener
class that is used in tests
class Listener():
def __init__(self):
...
def operation(self):
# actual listener
Currently, test looks the following way
@pytest.fixture
def event():
ee = EventEmitter()
lstr = Listener()
ee.subscribe({"event" : [lstr.operation]})
return lstr, ee
def test_emitter(event):
lstr = event[0]
ee = event[1]
ee.emit("event")
assert lstr.result == 7 # for example
In order to test event emitter, I need to check whether the inner state of the listener has changed after event propagation. Thus, I need two objects and I wonder if there is a better way to do this (maybe use two fixtures instead of one somehow) because this looks kinda ugly to me.
Answers:
You will probably need two fixtures in this case.
You can try the @pytest.yield_fixture
like:
@pytest.yield_fixture
def event():
...
yield <event_properties>
@pytest.yield_fixture
def listener(event):
...
yield <listener_properties>
Note: this is now deprecated https://docs.pytest.org/en/latest/yieldfixture.html
Usually in order to avoid tuples
and beautify your code, you can join them back together to one unit as a class, which has been done for you, using collections.namedtuple
:
import collections
EventListener = collections.namedtuple('EventListener', 'event listener')
Now modify your fixture:
@pytest.fixture
def event_listener():
e = EventListener(EventEmitter(), Listener())
e.event.subscribe({'event' : [e.listener.operation]})
return e
Now modify your test:
def test_emitter(event_listener):
event_listener.event.emit('event')
assert event_listener.listener.result == 7
You should use a Python feature called iterable unpacking into variables.
def test_emitter(event):
lstr, ee = event # unpacking
ee.emit("event")
assert lstr.result == 7
Basically, you are assigning event[0]
to lstr
, and event[1]
to ee
. Using this feature is a very elegant way to avoid using indexes.
Discarding
In case you are going to use your fixture in mutiple tests, and you don’t need all values in every test, you can also discard some elements of the iterable if you are not interested in using them as follows:
l = ['a', 'b', 'c', 'd']
a, b, c, d = l # unpacking all elements
a, _, c, d = l # discarding b
a, _, _, d = l # python 2: discard b and c
a, *_, d = l # python 3: discard b and c
a, _, _, _ = l # python2: discard, b, c and d
a, *_ = l # python3: discard b, c, and d
In theory, you are not literally discarding the values, but in Python _
, so-called “I don’t care”, is used for ignoring the specific values.
If you can not afford to easily split your tuple fixture into two independent fixtures, you can now “unpack” a tuple or list fixture into other fixtures using my pytest-cases
plugin as explained in this answer.
For your example that would look like:
from pytest_cases import pytest_fixture_plus
@pytest_fixture_plus(unpack_into="lstr,ee")
def event():
ee = EventEmitter()
lstr = Listener()
ee.subscribe({"event" : [lstr.operation]})
return lstr, ee
def test_emitter(lstr, ee):
ee.emit("event")
assert lstr.result == 7 # for example
I landed here when searching for a similar topic.
Due to lack of reputation I cannot comment on the answer from @lmiguelvargasf (https://stackoverflow.com/a/56268344/2067635) so I need to create a separate answer.
I’d also prefer returning multiple values and unpacking them to individual variables. This results in concise, Pythonic code.
There is one caveat, though:
For tests that rely on fixtures with autouse=True
, this results in a TypeError
Example:
@pytest.fixture(autouse=True)
def foo():
return 1, 2
# Gets fixture automagically through autouse
def test_breaks():
arg1, arg2 = foo
assert arg1 <= arg2
# Explicit request for fixture foo
def test_works(foo):
arg1, arg2 = foo
assert arg1 <= arg2
test_breaks
FAILED [100%]
def test_breaks():
> arg1, arg2 = foo
E TypeError: cannot unpack non-iterable function object
test_works
=============== 1 passed in 0.43s ===============
Process finished with exit code 0
PASSED [100%]
The fix is easy. But it took me some time to figure out what the problem was, so I thought I share my findings.
I am learning how to use pytest by testing a simple event emitter implementation.
Basically, it looks like this
class EventEmitter():
def __init__(self):
...
def subscribe(self, event_map):
# adds listeners to provided in event_map events
def emit(self, event, *args):
# emits event with given args
For convenience, I created a Listener
class that is used in tests
class Listener():
def __init__(self):
...
def operation(self):
# actual listener
Currently, test looks the following way
@pytest.fixture
def event():
ee = EventEmitter()
lstr = Listener()
ee.subscribe({"event" : [lstr.operation]})
return lstr, ee
def test_emitter(event):
lstr = event[0]
ee = event[1]
ee.emit("event")
assert lstr.result == 7 # for example
In order to test event emitter, I need to check whether the inner state of the listener has changed after event propagation. Thus, I need two objects and I wonder if there is a better way to do this (maybe use two fixtures instead of one somehow) because this looks kinda ugly to me.
You will probably need two fixtures in this case.
You can try the @pytest.yield_fixture
like:
@pytest.yield_fixture
def event():
...
yield <event_properties>
@pytest.yield_fixture
def listener(event):
...
yield <listener_properties>
Note: this is now deprecated https://docs.pytest.org/en/latest/yieldfixture.html
Usually in order to avoid tuples
and beautify your code, you can join them back together to one unit as a class, which has been done for you, using collections.namedtuple
:
import collections
EventListener = collections.namedtuple('EventListener', 'event listener')
Now modify your fixture:
@pytest.fixture
def event_listener():
e = EventListener(EventEmitter(), Listener())
e.event.subscribe({'event' : [e.listener.operation]})
return e
Now modify your test:
def test_emitter(event_listener):
event_listener.event.emit('event')
assert event_listener.listener.result == 7
You should use a Python feature called iterable unpacking into variables.
def test_emitter(event):
lstr, ee = event # unpacking
ee.emit("event")
assert lstr.result == 7
Basically, you are assigning event[0]
to lstr
, and event[1]
to ee
. Using this feature is a very elegant way to avoid using indexes.
Discarding
In case you are going to use your fixture in mutiple tests, and you don’t need all values in every test, you can also discard some elements of the iterable if you are not interested in using them as follows:
l = ['a', 'b', 'c', 'd']
a, b, c, d = l # unpacking all elements
a, _, c, d = l # discarding b
a, _, _, d = l # python 2: discard b and c
a, *_, d = l # python 3: discard b and c
a, _, _, _ = l # python2: discard, b, c and d
a, *_ = l # python3: discard b, c, and d
In theory, you are not literally discarding the values, but in Python _
, so-called “I don’t care”, is used for ignoring the specific values.
If you can not afford to easily split your tuple fixture into two independent fixtures, you can now “unpack” a tuple or list fixture into other fixtures using my pytest-cases
plugin as explained in this answer.
For your example that would look like:
from pytest_cases import pytest_fixture_plus
@pytest_fixture_plus(unpack_into="lstr,ee")
def event():
ee = EventEmitter()
lstr = Listener()
ee.subscribe({"event" : [lstr.operation]})
return lstr, ee
def test_emitter(lstr, ee):
ee.emit("event")
assert lstr.result == 7 # for example
I landed here when searching for a similar topic.
Due to lack of reputation I cannot comment on the answer from @lmiguelvargasf (https://stackoverflow.com/a/56268344/2067635) so I need to create a separate answer.
I’d also prefer returning multiple values and unpacking them to individual variables. This results in concise, Pythonic code.
There is one caveat, though:
For tests that rely on fixtures with autouse=True
, this results in a TypeError
Example:
@pytest.fixture(autouse=True)
def foo():
return 1, 2
# Gets fixture automagically through autouse
def test_breaks():
arg1, arg2 = foo
assert arg1 <= arg2
# Explicit request for fixture foo
def test_works(foo):
arg1, arg2 = foo
assert arg1 <= arg2
test_breaks
FAILED [100%]
def test_breaks():
> arg1, arg2 = foo
E TypeError: cannot unpack non-iterable function object
test_works
=============== 1 passed in 0.43s ===============
Process finished with exit code 0
PASSED [100%]
The fix is easy. But it took me some time to figure out what the problem was, so I thought I share my findings.