Skip to content

Replace _get_url abstract test method with a pytest fixture#751

Open
sbland wants to merge 1 commit into
mainfrom
750-use-url-fixture-instead-of-abstract-method-in-tests
Open

Replace _get_url abstract test method with a pytest fixture#751
sbland wants to merge 1 commit into
mainfrom
750-use-url-fixture-instead-of-abstract-method-in-tests

Conversation

@sbland

@sbland sbland commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

Replaces the use of the _get_url abstract method with a def url(self)... fixture. This allows accessing other fixture data such as database mocked instances.

Fixes #750

Type of change

  • Documentation (non-breaking change that adds or improves the documentation)
  • New feature (non-breaking change which adds functionality)
  • Optimization (non-breaking, back-end change that speeds up the code)
  • Technical work (non-breaking, change which is work as part of a new feature)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (whatever its nature)

Key checklist

  • All tests pass (eg. python -m pytest)
  • The documentation builds and looks OK (eg. mkdocs serve)
  • Pre-commit hooks run successfully (eg. pre-commit run --all-files)

Further checks

  • Code is commented, particularly in hard-to-understand areas
  • Tests added or an issue has been opened to tackle that in the future. (Indicate issue here: # (issue))

@sbland
sbland requested a review from AdrianDAlessandro June 22, 2026 20:58
@sbland

sbland commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@AdrianDAlessandro I needed to implement this to do tests where we needed to pass url params to the _get_url method, particularly for beautiful soup tests.

Also there was a failing test on the main branch. I'm not sure if this is just local to me.

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@AdrianDAlessandro AdrianDAlessandro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure exactly what this is trying to fix. The issue says it's to be able to pass arguments to the _get_url method, but that is already done in some places. I've commented on the parts that do it.

Comment on lines -500 to -501
def _get_url(self, **kwargs):
return reverse("skill_detail", kwargs=kwargs)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is what you are trying to do achieved by this existing pattern?

def test_template_used(self, admin_client, url):
"""Test the correct template is used by the GET request."""
with assertTemplateUsed(template_name=self._template_name):
response = admin_client.get(self._get_url(slug=skill.slug))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Results in being able to pass args to reverse like this

@sbland

sbland commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

I'm not sure exactly what this is trying to fix. The issue says it's to be able to pass arguments to the _get_url method, but that is already done in some places. I've commented on the parts that do it.

The existing method works if you are calling _get_url from inside the class that defines get_url but doesn't work when we use _get_url in a fixture. Example here: https://github.com/direct-framework/direct-webapp/blob/main/tests/main/view_utils.py#L63

The use case I couldn't implement is when the url should use params that are defined from mocked database instance objects.

Edit:

class BS4Mixin(ABC):
    """Mixin for tests that use BeautifulSoup4. It makes the `soup` fixture available.

    Note: Using this requires the test class to define:
        - A `_get_url` method
    """

    @abstractmethod
    def _get_url(self) -> str:
        return NotImplemented

    @pytest.fixture
    def soup(self, client) -> BeautifulSoup:
        """A fixture of the BeautifulSoup4 object of the requested page."""
        response = client.get(self._get_url()) # << This does not allow passing args
        return BeautifulSoup(response.content, "html.parser")
...

@sbland
sbland marked this pull request as ready for review July 16, 2026 09:50

@AdrianDAlessandro AdrianDAlessandro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this approach because it breaks the intention behind the current approach. I have suggested an alternative in the comment below. What do you think @sbland? Does that also solve the problem?

Comment on lines +549 to +555
class TestSkillPageView404:
"""Tests related to the 404 error for non-existent skills."""

@pytest.fixture
def url(self):
"""Fixture to get the test url for a non-existent skill."""
return reverse("skill_detail", kwargs={"slug": "nonexistent-skill"})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of needing to create a whole new class just for testing a different query parameter for the same url. I think the point of these classes and mixing is to be able to group all the relevant testing for each base url into the one class.

Comment thread tests/main/view_utils.py
Comment on lines +53 to 56
def soup(self, client, url) -> BeautifulSoup:
"""A fixture of the BeautifulSoup4 object of the requested page."""
response = client.get(self._get_url())
response = client.get(url)
return BeautifulSoup(response.content, "html.parser")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still results in not being able to pass arguments to client.get without create a whole new class with a different url fixture. Which would be the same as creating a whole new class with a different _get_url method. And (as mentioned in another comment), I'm not a fan of needing to create separate classes for all of the edge cases.

I think maybe the best solution is to change these soup fixtures (because I think they're the only problematic ones) into factory fixtures.

For example (requires a from collections.abc import Callable at the top):

    @pytest.fixture
    def soup_factory(self, client) -> Callable[..., BeautifulSoup]:
        """A fixture factory for the BeautifulSoup4 object of the requested page."""

        def get_soup(**kwargs) -> BeautifulSoup:
            if kwargs:
                response = client.get(self._get_url(kwargs=kwargs))
            else:
                response = client.get(self._get_url())
            return BeautifulSoup(response.content, "html.parser")

        return get_soup

Which can then be called as a function inside the test using it. There could also be a default one that is using it without arguments, to keep the current behaviours consistent:

    @pytest.fixture
    def soup(self, soup_factory) -> BeautifulSoup:
        """A fixture of the BeautifulSoup4 object of the requested page."""
        return soup_factory()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use url fixture instead of abstract method in tests

2 participants