Using Multiple Fixtures

You’ve already seen that tmpdir uses tmpdir_factory. And you used tmpdir in our tasks_db fixture. Let’s keep the chain going and add some specialized fixtures for non-empty tasks databases:

ch3/a/tasks_proj/tests/conftest.py
 @pytest.fixture()
 def​ db_with_3_tasks(tasks_db, tasks_just_a_few):
 """Connected db with 3 tasks, all unique."""
 for​ t ​in​ tasks_just_a_few:
  tasks.add(t)
 
 
 @pytest.fixture()
 def​ db_with_multi_per_owner(tasks_db, tasks_mult_per_owner):
 """Connected db with 9 tasks, 3 owners, all with 3 tasks."""
 for​ t ​in​ tasks_mult_per_owner:
  tasks.add(t)

These fixtures all include two fixtures each in their parameter list: tasks_db and a data set. The data set is used to add tasks to the database. Now tests can use these when you want the test to start from a non-empty database, like this:

ch3/a/tasks_proj/tests/func/test_add.py
 def​ test_add_increases_count(db_with_3_tasks):
 """Test tasks.add() affect on tasks.count()."""
 # GIVEN a db with 3 tasks
 # WHEN another task is added
  tasks.add(Task(​'throw a party'​))
 
 # THEN the count increases by 1
 assert​ tasks.count() == 4

This also demonstrates one of the great reasons to use fixtures: to focus the test on what you’re actually testing, not on what you had to do to get ready for the test. I like using comments for GIVEN/WHEN/THEN and trying to push as much GIVEN into fixtures for two reasons. First, it makes the test more readable and, therefore, more maintainable. Second, an assert or exception in the fixture results in an ERROR, while an assert or exception in a test function results in a FAIL. I don’t want test_add_increases_count() to FAIL if database initialization failed. That would just be confusing. I want a FAIL for test_add_increases_count() to only be possible if add() really failed to alter the count. Let’s trace it and see all the fixtures run:

 $ ​​cd​​ ​​/path/to/code/ch3/a/tasks_proj/tests/func
 $ ​​pytest​​ ​​--setup-show​​ ​​test_add.py::test_add_increases_count
 ===================== test session starts ======================
 collected 1 item
 
 test_add.py
 SETUP S tmpdir_factory
  SETUP F tmpdir (fixtures used: tmpdir_factory)
  SETUP F tasks_db (fixtures used: tmpdir)
  SETUP F tasks_just_a_few
  SETUP F db_with_3_tasks (fixtures used: tasks_db, tasks_just_a_few)
  func/test_add.py::test_add_increases_count
  (fixtures used: db_with_3_tasks, tasks_db, tasks_just_a_few,
  tmpdir, tmpdir_factory).
  TEARDOWN F db_with_3_tasks
  TEARDOWN F tasks_just_a_few
  TEARDOWN F tasks_db
  TEARDOWN F tmpdir
 TEARDOWN S tmpdir_factory
 
 =================== 1 passed in 0.04 seconds ===================

There are those F’s and S’s for function and session scope again. Let’s learn about those next.