{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/python/compose-multiple-levels-of-pytest-fixtures/",
  "description": "Combine session and function-scoped pytest fixtures to avoid expensive test setup while maintaining test isolation and preventing state coupling.",
  "path": "/python/compose-multiple-levels-of-pytest-fixtures/",
  "publishedAt": "2022-07-21T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Python",
    "Testing",
    "TIL",
    "Pytest"
  ],
  "textContent": "While reading the second version of [Brian Okken's pytest book], I came across this neat\ntrick to compose multiple levels of fixtures. Suppose, you want to create a fixture that\nreturns some canned data from a database. Now, let's say that invoking the fixture multiple\ntimes is expensive, and to avoid that you want to run it only once per test session.\nHowever, you still want to clear all the database states after each test function runs.\nOtherwise, a test might inadvertently get coupled with another test that runs before it via\nthe fixture's shared state. Let's demonstrate this:\n\nIn the above snippet, we've created a session-scoped fixture called create_files that\ncreates three files in a temporary directory, writes some content to them, and then yields\nthe directory. Afterward, we write two tests where the first one tests the files' default\ncontent and the second one writes some stuff to each of the file and then test their\ncontent.\n\nIf we run this with pytest, both of the tests pass. However, if we change the order of the\ntests where the test_read_custom_content runs before test_read_default_content, pytest\nwill raise an error:\n\nOur tests behave differently when the order of their execution changes. This is bad. You\nshould always make sure that running your tests randomly or reversely doesn't change the\noutcome of the test run. You can use a plugin like [pytest-reverse] to change your test\nexecution order.\n\nThis happens because the data of the fixture create_files persists across multiple tests\nsince it's defined as a session-scoped fixture. Here, test_read_custom_content overwrites\nthe default contents of the files and when the other test runs after this one, it can't find\nthe default content and hence raises an AssertionError. To fix this, we'll need to make\nsure that the fixture's state gets cleaned up after each test function executes.\n\nOne way to achieve this is by making the create_files fixture function-scoped; instead of\nsession-scoped. If you decorate create_files with @pytest.fixture(scope=\"function\") and\nthen run the above snippet in a reverse manner, you'll see that the error doesn't occur this\ntime. However, making the fixture function-scoped means, the fixture will be executed once\nbefore running each test function. This can be a deal breaker if the fixture has to perform\nsome time-consuming setups.\n\nTo solve this, we can keep the create_files fixture session-scoped and use another\nfunction-scoped fixture to clean up its state. This way, before running each test function,\nthe function-scoped fixture will clean up the state of the session-scoped fixture. We can\nwrite the previous example as follows:\n\nNotice that I've swapped the order of the tests just for demonstration purposes. Here, we've\ndefined another fixture called get_files which is function-scoped. Underneath, get_files\nuses create_files to create the file contents and then cleans up the state after the yield\nstatement. We could refactor some of the clean-up code to make it DRY but I intentionally\nkept it verbose for simplicity's sake.\n\nIn this case, the lighter get_files fixture gets executed before every test function runs\nand keeps the state of the create_files clean. On the other hand, the create_files\nfixture gets executed only once per test session. This time, if you run the tests, all the\ntests should pass successfully. We have successfully composed two different levels of\nfixture functions!\n\n\n\n\n[brian okken's pytest book]:\n    https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition/\n\n[pytest-reverse]:\n    https://github.com/adamchainz/pytest-reverse",
  "title": "Compose multiple levels of fixtures in pytest"
}