2

I am learning pytest and studying the behavior of different fixture scopes. I am seeing unexpected behavior of class scoped fixtures when I run the tests. This is my project structure.

Pytest_Basics
│   conftest.py
└───pack1
        test_a.py
        test_b.py

Here are contents of each file.

conftest.py

import pytest


@pytest.fixture(scope='session', autouse=True)
def ses_fix():
    print('In session fixture')


@pytest.fixture(scope='package', autouse=True)
def pak_fix():
    print('In package fixture')


@pytest.fixture(scope='module', autouse=True)
def mod_fix():
    print('In module fixture')


@pytest.fixture(scope='class', autouse=True)
def cls_fix():
    print('In class fixture')


@pytest.fixture(scope='function', autouse=True)
def func_fix():
    print('In functon fixture')

test_a.py

class TestA:

    def test_a1(self):
        assert True

    def test_a2(self):
        assert True

    def test_a3(self):
        assert True

test_b.py

def test_b1():
    assert True


def test_b2():
    assert True


def test_b3():
    assert True

When I run the test using pytest -v -s. I get below output.

pack1/test_a.py::TestA::test_a1 In session fixture
In package fixture
In module fixture
In class fixture
In functon fixture
PASSED
pack1/test_a.py::TestA::test_a2 In functon fixture
PASSED
pack1/test_a.py::TestA::test_a3 In functon fixture
PASSED
pack1/test_b.py::test_b1 In module fixture        
In class fixture
In functon fixture
PASSED
pack1/test_b.py::test_b2 In class fixture
In functon fixture
PASSED
pack1/test_b.py::test_b3 In class fixture
In functon fixture
PASSED

I expected class scoped fixture will run only once as I have only one class in test_a.py module. However, I am seeing it is running while executing tests in test_b.py module.

What is causing this behavior? Is this a bug or I have limited understanding of class level fixtures.

Environment: Python - 3.9.5, Pytest - 6.2.4

thatisvivek
  • 875
  • 1
  • 12
  • 30

1 Answers1

5

This is indeed how class-scoped fixtures behave. It isn't directly mentioned in the documentation, but it may be inferred from it. As stated:

Fixtures are created when first requested by a test, and are destroyed based on their scope

Fixtures with autouse=True are applied to every test function in the session. They are destroyed based on their scope, meaning that session-, module- or function-based fixtures are destroyed at the end of the session, module or function, where every test function lives. Class based fixtures, however, can be invoked outside of classes, and to be consistent, they must be destroyed after the function in this case - otherwise they could be never destroyed (if there are no classes), or would live across class boundaries. The important point is that class-scope (or any other scope) does not mean that the fixtures are applied only in that scope (e.g. inside a class), but to any test function, and the scope is only about the time it is destroyed.

For functions that do not live in a class, class-scoped fixtures behave just like function-scoped fixtures, but they are invoked before the function-scoped fixtures and shut down after the function-based fixtures:

conftest.py

@pytest.fixture(scope="function", autouse=True)
def fct_fixt():
    print("function fixture start")
    yield
    print("function fixture end")


@pytest.fixture(scope="class", autouse=True)
def class_fixt():
    print("class fixture start")
    yield
    print("class fixture end")

test_fixt.py

def test_1():
    print("test_1 outside class")

class TestClass:
    def test_1(self):
        print("test_1 inside class")

    def test_class_2(self):
        print("test_2 inside class")
$ python -m pytest -s test_fixt.py

...
class fixture start
  function fixture start
    test_1 outside class
  function fixture end
class fixture end
class fixture start
  function fixture start
    test_1 inside class
  function fixture end
  function fixture start
    test_2 inside class
  function fixture end
class fixture end

(indentation added for clarity)

MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • Thank you @MrBean Bremen. I understood the present behavior. However, I still didn't get why they are behaving that way i.e. class scoped fixture is getting executed before test which is not part of class. – thatisvivek May 29 '21 at 19:01
  • @vivek_ratnaparkhi: I added an explanation why it behaves that way - please check if that is clear enough. – MrBean Bremen May 30 '21 at 10:06
  • Thanks @MrBean Bremen for explanation. This is very helpful – thatisvivek Jun 01 '21 at 04:24
  • @MrBeanBremen The problem is when you call class scoped parametrized fixture in a regular class scoped fixture. The class scoped fixture is destroyed before the class end. Can you please explain this? – igor Jun 29 '22 at 04:31
  • @MrBeanBremen Please see [question](https://stackoverflow.com/questions/72795913/unexpected-behavior-of-class-scoped-fixture-when-calling-a-class-scoped-parametr) – igor Jun 29 '22 at 04:55