Pytest fixture for a class through self not as method argument
Solution 1
Sure, just use an autouse fixture. Here is the relevant spot in pytest
docs. In your example, the change would be introducing an extra fixture (I named it _request_google_page
):
from bs4 import BeautifulSoup
import pytest
import requests
@pytest.fixture()
def google():
return requests.get("https://www.google.com")
class TestGoogle:
@pytest.fixture(autouse=True)
def _request_google_page(self, google):
self._response = google
def test_alive(self):
assert self._response.status_code == 200
def test_html_title(self):
soup = BeautifulSoup(self._response.content, "html.parser")
assert soup.title.text.upper() == "GOOGLE"
You could even drop the google
fixture completely and move the code to _request_google_page
:
@pytest.fixture(autouse=True)
def _request_google_page(self):
self._response = requests.get("https://www.google.com")
Note that _request_google_page
will be called once per test by default, so each test will get a new response. If you want the response to be initialized once and reused throughout all tests in the TestGoogle
class, adjust the fixture scopes (scope='class'
for _request_google_page
and scope='module'
or scope='session'
for google
). Example:
from bs4 import BeautifulSoup
import pytest
import requests
@pytest.fixture(scope='module')
def google():
return requests.get("https://www.google.com")
@pytest.fixture(autouse=True, scope='class')
def _request_google_page(request, google):
request.cls._response = google
class TestGoogle:
def test_alive(self):
assert self._response.status_code == 200
def test_html_title(self):
soup = BeautifulSoup(self._response.content, "html.parser")
assert soup.title.text.upper() == "GOOGLE"
Solution 2
I had to solve a similar problem and the accepted solution didn't work for me with a class-scoped fixture.
I wanted to call a fixture once per test class and re-use the value in test methods using self
. This is actually what the OP was intending to do as well.
You can use the request
fixture to access the class that's using it (request.cls
) and assign the fixture value in a class attribute. Then you can access this attribute from self
. Here's the full snippet:
from bs4 import BeautifulSoup
import pytest
import requests
@pytest.fixture(scope="class")
def google(request):
request.cls.google = requests.get("https://www.google.com")
@pytest.mark.usefixtures("google")
class TestGoogle:
def test_alive(self):
assert self.google.status_code == 200
def test_html_title(self):
soup = BeautifulSoup(self.google.content, "html.parser")
assert soup.title.text.upper() == "GOOGLE"
Hope that helps anyone else coming to this question.
Donal
Updated on August 13, 2021Comments
-
Donal over 2 years
Often I'll write a test class that uses a pytest fixture in every method. Here's an example. I'd like to be able to avoid having to write the fixture name in the signature of every method. It's not DRY. How can this be done?
I would like to be able to access the fixture by giving the fixture as an attribute of the test class. In this example, I would like to see the google fixture as an attribute of TestGoogle. Is this possible?
from bs4 import BeautifulSoup import pytest import requests @pytest.fixture() def google(): return requests.get("https://www.google.com") class TestGoogle: def test_alive(self, google): assert google.status_code == 200 def test_html_title(self, google): soup = BeautifulSoup(google.content, "html.parser") assert soup.title.text.upper() == "GOOGLE"
-
Donal almost 6 yearsThanks @hoefling That's what I wanted. I never understood the autouse fixture functionality. I understand now. Cheers.
-
hoefling almost 6 yearsGlad I could help you!
-
Ken4scholars over 5 years@hoefling for some reason this never works for me. The test methods seem to be getting a new instance and
self
does not have the attributes in the setup method. Just for context, I'm using 2pytext.mark...
on the class. -
hoefling over 5 years@Ken4scholars best is to ask a separate question, including a minimal reproducible example of the failing test setup you have. Can you do that?
-
Ken4scholars over 5 years@hoefling, sure. Please check this question. Though it is another issue there, but it has the description of this issue. stackoverflow.com/questions/54192519/…
-
Prashant Pathak over 3 years@hoefling, When I set the scope _request_google_page to 'class' and the scope of google to "module", I see the following error inside the function "def test_alive(self):" AttributeError: 'TestGoogle' object has no attribute '_response' Any idea?
-
hoefling over 3 years@PrashantPathak I have added a snippet with adjusted scopes at the end of the answer, check it out. It will work when you copy it as-is.
-
Prashant Pathak over 3 years@hoefling, Thanks, So it means the function should be outside the class scope.
-
Prashant Pathak over 3 years@hoefling Can you also help me to understand one concept? which I am getting hard time to clear. So in the above code def _request_google_page() does not return anything still we are able to use response object in def test_alive(). So how the "request.cls._response" is getting accessed inside the class test methods, how is the call follow?
-
hoefling over 3 years@PrashantPathak this is part of test collection and preparation.
request.cls
will beTestGoogle
when the_request_google_page
fixture is evaluated on test collection, so afterTestGoogle
is collected,TestGoogle._response
will be initialized; after that,TestGoogle().test_alive()
is executed andself._response
is available. It's a class variable, actually. -
Prashant Pathak over 3 years@hoefling, Ohh I see so this "self" here is not an instance variable it's basically pointing to the class variable. Got it now. Thanks for your clarification
-
MohitC about 3 yearsHowever there is not way to set the value of
self.variable
through this? -
makeiteasy about 3 years@MohitC I don't think there is and I can't think of a case when it would be needed and
cls
wouldn't work -
Jorrit Smit about 3 yearsi actually want exactly this behaviour. set the value of the self.variable in a test and use that result in following tests
-
Anurag jain about 3 yearsI am facing another issue with it ScopeMismatch: You tried to access the 'function' scoped fixture with a 'class' scoped request object, involved factories
-
hoefling about 3 years@Anuragjain this means you have broadened the scope in a chain of fixtures invocation, which isn't allowed. This is not an issue with the code in the answer; if you need help, ask a new question with a minimal reproducible example.
-
Jan Sakalos almost 3 years@JorritSmit sounds strange to set a value in one test to be used in other tests. A test should test not generate values for other tests.