python-3.xdjangotestingfixturespytest-django

How to Access Pytest Fixtures passed into Pytest Classes with the pytest.mark.usefixture marker


I wrote a base test class to be used to test multiple viewsets that all depended on different fixtures, this meant i did not want to directly pass in the fixtures into the test class methods as arguments and need to pass the fixtures as the class level for each of the subclass of the base test class, but doing so has prevented easy access of the fixture as a class/instance attribute in the class methods.

this is a snippet of the base test class

class BaseViewSetTest(TestHelper):
    """
    Base Mixin Test Class to make testing viewset endpoints enjoyable.

    inherit the class in the test class you wish to use for a viewset,
    provide the base_url for the viewset and the create_data needed to
    make a post request to create a data instance linked to the viewset, 
    provide the api_fields list exposed by the serializer of the viewset.
    """
    base_url = ""
    create_data = {}
    api_fields = []

    def test_list_endpoint(self, authenticated_user: tuple[User, APIClient]):
        user, client = authenticated_user
        url = reverse(f"{self.base_url}-list")
        response: Response = client.get(url)
        assert response.status_code == 200
        response_data = response.json()
        # more tests goes here..


@pytest.mark.django_db
@pytest.mark.usefixtures("business")
class TestBusinessViewSet(BaseViewSetTest):
    base_url = "business"
    create_data = {"name": "InfoSec", "email": "ema@gmail.com", "phone_number": "09037637633"}
    api_fields = ["id", "name", "email", "address", "phone_number", "currency"]        

Now i want to be able to access the fixture passed into the classes such that i can manipulate these fixtures in the base class to better set the tests.

for example i want to be able to set the user attribute of a fixture instance to point at the user attribute of the current test session (user is a fixture data).


Solution

  • I found that the fixture passed into a pytest test class can be accessed through the pytestmark instance attribute, albiet being in the string form, using this string in conjunction with the request fixture, the actual fixture result can be evaluated (this must be the similar way fixtures are evaluated when passed as method arguments)

    NOTE: the selection of the first pytestmark item here is very intentional and might not work for you, to ensure it works pass the fixture as the first decorator (deepest decorator) when using multiple decorators.

    class BaseViewSetTest(TestHelper):
        """
        Base Mixin Test Class to make test viewset endpoints enjoyable.
    
        inherit the class in the test class you wish to use for a viewset
        provide the base_url for the viewset and the create_data needed to
        make a post request to create a data instance linked to the viewset, 
        provide the api_fields exposed by the serializer of the viewset.
        """
        base_url = ""
        create_data = {}
        api_fields = []
        user_related_field = "user"
    
        @pytest.fixture
        def model_instance(self, request):
            fixture_str = self.pytestmark[0].args[0]
            instance = request.getfixturevalue(f"{fixture_str}")
            return instance
    
        def test_list_endpoint(self, authenticated_user: tuple[User, APIClient], model_instance):
            user, client = authenticated_user
            # set the current user to be related to the model instance
            setattr(model_instance, self.user_related_field, user)
            model_instance.save()
            url = reverse(f"{self.base_url}-list")
            response: Response = client.get(url)
            assert response.status_code == 200
            response_data = response.json()
            # more tests goes here
    
    @pytest.mark.django_db
    @pytest.mark.usefixtures("business")
    class TestBusinessViewSet(BaseViewSetTest):
        base_url = "business"
        create_data = {"name": "InfoSec", "email": "ema@gmail.com", "phone_number": "09037637633"}
        api_fields = ["id", "name", "email", "address", "phone_number", "currency"]
        user_related_field = "owner"