pythonpytestbddpytest-bdd

why is this method being called twice (pytest)


I have a class called Tree and a class called Property in models.py:

class Tree:
    def __init__(self, name=None):
        self.name = name
        self.properties = []

    def get_name(self):
        return self.name

    def add_property(self, prop):
        if prop.__class__.__name__ != 'Property':
            raise TypeError('Only properties may be added as properties')
        else:
            print('adding property: ' + str(prop))
            self.properties.append(prop)
            
    def add_properties(self, arr):
        for label in arr:
            property = Property(label=label)
            self.add_property(property)

    def get_properties(self):
        return self.properties

class Property:
    def __init__(self, label=None):
        self.label = label
        self.tree = None
        self.values = []


    def __str__(self):
        return self.label

I am trying to test these classes using the following pytest test script, which takes inputs from a .feature file:

import pytest
from pytest_bdd import scenario, given, when, then, parsers
from models import Tree

@scenario('../features/Tree.feature',
          'add properties to a tree')
def test_tree():
    pass

@given("a new tree is created", target_fixture="create_tree")
def create_tree():
    return Tree()

@pytest.fixture()
@when(parsers.cfparse("{properties} are added as properties"))
def add_properties(create_tree, properties):
    properties = properties.split(',')
    create_tree.add_properties(properties)
    return create_tree

@then("I can get these properties")
def publish_article(add_properties):
    result = add_properties.get_properties()
    assert len(result) == 3

Tree.feature

Feature: Tree

# BUILD TOP-DOWN 

Scenario: add properties to a tree
  Given a new tree is created
  When height, weight, age are added as properties
  Then I can get these properties

Somehow, the add_properties method in the when step is being called twice. I know this because the following is printed:

add_properties = <models.Tree object at 0x10b792760>

    @then("I can get these properties")
    def publish_article(add_properties):
        print(add_properties.get_properties())
>       assert len(add_properties.get_properties()) == 3
E       assert 6 == 3
E        +  where 6 = len([<models.Property object at 0x10b792b50>, <models.Property object at 0x10b792a00>, <models.Property object at 0x10b792...dels.Property object at 0x10b62a280>, <models.Property object at 0x10b62a040>, <models.Property object at 0x10b62a0d0>])
E        +    where [<models.Property object at 0x10b792b50>, <models.Property object at 0x10b792a00>, <models.Property object at 0x10b792...dels.Property object at 0x10b62a280>, <models.Property object at 0x10b62a040>, <models.Property object at 0x10b62a0d0>] = <bound method Tree.get_properties of <models.Tree object at 0x10b792760>>()
E        +      where <bound method Tree.get_properties of <models.Tree object at 0x10b792760>> = <models.Tree object at 0x10b792760>.get_properties

tests/step_defs/test_tree.py:26: AssertionError
----------------------------- Captured stdout call -----------------------------
adding property: height
adding property:  weight
adding property:  age
adding property: height
adding property:  weight
adding property:  age
[<models.Property object at 0x10b792b50>, <models.Property object at 0x10b792a00>, <models.Property object at 0x10b792a60>, <models.Property object at 0x10b62a280>, <models.Property object at 0x10b62a040>, <models.Property object at 0x10b62a0d0>]
=========================== short test summary info ============================
FAILED tests/step_defs/test_property.py::test_property - pytest_bdd.exception...
FAILED tests/step_defs/test_tree.py::test_tree - assert 6 == 3
========================= 2 failed, 1 passed in 0.24s ========================== 

Why are the properties being added twice?


Solution

  • I think the problem is that pytest_bdd lets you use the @given as fixtures but you also added the @pytest.fixture decorator on the @when.

    That means the add_properties function is called once as the when step and then it is called again as a pytest fixture. Remove the extra decorator and use the original create_tree fixture instead.

    import pytest
    from pytest_bdd import scenario, given, when, then, parsers
    from models import Tree
    
    @scenario('../features/Tree.feature',
              'add properties to a tree')
    def test_tree():
        pass
    
    @given("a new tree is created", target_fixture="create_tree")
    def create_tree():
        return Tree()
    
    @when(parsers.cfparse("{properties} are added as properties"))
    def add_properties(create_tree, properties):
        properties = properties.split(',')
        create_tree.add_properties(properties)
        return create_tree
    
    @then("I can get these properties")
    def publish_article(create_tree):
        result = create_tree.get_properties()
        assert len(result) == 3