Search code examples
pythontestingpytestpytest-fixtures

How to parametrize test using different data from two different fixtures in pytest


I have several pytest fixtures:

@pytest.fixture
def articles_new(category):
    article_new_1 = ArticleFactory.create(category=category["category_1"], status=ArticleStatusChoices.NEW)
    article_new_2 = ArticleFactory.create(category=category["category_2"], status=ArticleStatusChoices.NEW)
    return {"article_new_1": article_new_1, "article_new_2": article_new_2}

@pytest.fixture
def articles_published_with_readers(category):
    article_published_with_readers_1 = ArticleFactory.create(
        category=category["category_2"],
        readers=ArticleRoleTypesChoices.EMPLOYEE,
        status=ArticleStatusChoices.PUBLISHED,
    )
    return {"article_published_with_readers_1": article_published_with_readers_1}

@pytest.fixture
def category(product):
    category_1 = CategoryFactory.create(product=product["product_1"])
    category_2 = CategoryFactory.create(product=product["product_2"])
    return {"category_1": category_1, "category_2": category_2}

@pytest.fixture
def product():
    product_1 = ProductFactory.create()
    product_2 = ProductFactory.create()
    return {"product_1": product_1, "product_2": product_2}

I try to refactor such test:

def test_articles(self, client, user):
    client.force_login(user)
    
    res = client.get(reverse("get_article", kwargs={"article_guid": article_new_1.guid}))
    assert res.status_code == 200

    res = client.get(reverse("get_article", kwargs={"article_guid": article_published_with_readers_1.guid}))
    assert res.status_code == 403

into something like that:

@pytest.mark.parametrize(
        "article, expected",
        [
            ......
        ],
    )
def test_articles(self, client, user, article, expected):
    client.force_login(user)
    
    res = client.get(reverse("get_article", kwargs={"article_guid": article.guid}))
    assert res.status_code == expected

How can this problem be solved?
Note: article_new_1 and article_published_with_readers_1 have different categories.


Solution

  • Here is one way to solve this:

    import logging
    
    import pytest
    
    
    @pytest.fixture
    def client(): ...
    
    
    @pytest.fixture
    def user(): ...
    
    
    @pytest.fixture
    def category(product):
        category_1 = "category_1"
        category_2 = "category_2"
        return {"category_1": category_1, "category_2": category_2}
    
    
    @pytest.fixture
    def product():
        product_1 = "product_1"
        product_2 = "product_2"
        return {"product_1": product_1, "product_2": product_2}
    
    
    def create_new_article(category):
        return f"article with category {category['category_1']}"
    
    
    def publish_article(category):
        return f"published article with category {category['category_1']}"
    
    
    @pytest.mark.parametrize(
        "article_type, expected",
        [
            pytest.param("new", 200, id="success case"),
            pytest.param("published", 403, id="failed case"),
        ],
    )
    def test_article(client, user, category, article_type, expected):
        logging.debug("article_type=%r", article_type)
        if article_type == "new":
            article = create_new_article(category)
        elif article_type == "published":
            article = publish_article(category)
        else:
            raise ValueError("Invalid article type")
    
        # Do something with article and expected
        logging.debug("article=%r", article)
        logging.debug("expected=%r", expected)
        # assert res.status_code == expected
    

    Output:

    test_it.py::test_article[success case]
    -------------------------------------------- live log call --------------------------------------------
    DEBUG    root:test_it.py:44 article_type='new'
    DEBUG    root:test_it.py:53 article='article with category category_1'
    DEBUG    root:test_it.py:54 expected=200
    PASSED
    test_it.py::test_article[failed case]
    -------------------------------------------- live log call --------------------------------------------
    DEBUG    root:test_it.py:44 article_type='published'
    DEBUG    root:test_it.py:53 article='published article with category category_1'
    DEBUG    root:test_it.py:54 expected=403
    PASSED
    
    ========================================== 2 passed in 0.01s ==========================================
    

    Notes

    • Instead of passing into the test the actual article, I passed in article_type: "new" or "published". In the test, I will use this article type to determine which type of article to create.

    • create_new_article() and publish_article() are not fixtures, they are just normal functions

    • I mocked those fixtures/functions/classes which I don't have access to

    • Instead of

        ("new", 200)
      

      I use

        pytest.param("new", 200, id="success case")
      

      This allow the flexibility of given each parametrized case a name such as "success case" or "failed case"

    • This solution is a bit messy with a big if block used to create the article instead of passing the article directly into the test, but it is simple and easy to understand.