Reputation: 3773
I have a test that makes a thing, validates it, deletes the thing and confirms it was deleted.
def test_thing():
thing = Thing() # Simplified, it actually takes many lines to make a thing
assert thing.exists
thing.delete() # Simplified, it also takes a few lines to delete it
assert thing.deleted
Next I want to make many more tests that all use the thing, so it's a natural next step to move the thing creation/deletion into a fixture
@pytest.fixture
def thing():
thing = Thing() # Simplified, it actually takes many lines to make a thing
yield thing
thing.delete() # Simplified, it also takes a few lines to delete it
def test_thing(thing):
assert thing.exists
def test_thing_again(thing):
# Do more stuff with thing
...
But now I've lost my assert thing.deleted
.
I feel like I have a few options here, but none are satisfying.
ERROR
instead of a FAIL
.Fixture called directly
exception so I could move the thing creation out into a generator that is used by both the fixture and the test. This feels clunky though and what happens if my thing
fixture needs to use another fixture?What is my best option here? Is there something I haven't thought of?
Upvotes: 5
Views: 3868
Reputation: 3773
Another solution could be to have a single fixture that yields a context manager, so the test can be in full control of invoking it.
@pytest.fixture
def gen_thing():
@contextmanager
def cm():
thing = Thing() # Simplified, it actually takes many lines to make a thing
try:
yield thing
finally:
thing.delete() # Simplified, it also takes a few lines to delete it
yield cm
def test_thing(gen_thing):
with gen_thing() as thing:
assert thing.exists
assert thing.deleted
def test_thing_again(gen_thing):
with gen_thing() as thing:
# Do more stuff with thing
Creating the context manager as a closure means it would have the same scope as the fixture too.
Upvotes: 2
Reputation: 3735
If you want to test that a "thing" is deleted, make a fixture without teardown, delete it in the test, then assert if it is deleted.
@pytest.fixture
def thing_create():
# Perform all the creation steps
thing = Thing()
...
yield thing
def thing_delete(thing):
# Perform all the deletion steps
...
thing.delete()
@pytest.fixture
def thing_all(thing_create):
yield thing_create
thing_delete(thing_create)
def test_thing(thing_all):
assert thing_all.exists
def test_thing_again(thing_create):
thing_delete(thing_create)
assert thing_create.deleted
Upvotes: 5
Reputation: 395
How about using the fixture where appropriate (to reduce duplication) but not where your logic only exists once.
@pytest.fixture
def thing():
thing = Thing() # Simplified, it actually takes many lines to make a thing
yield thing
thing.delete() # Simplified, it also takes a few lines to delete it
def test_thing(thing):
assert thing.exists
def test_thing_does_a_thing(thing):
expected = "expected"
assert thing.do_thing() == expected
def test_thing_deletes():
# just don't use the fixture here
thing = Thing()
thing.delete()
assert thing.deleted
Upvotes: 2