Choice (Word/String Alternatives)

Assume you need a user-defined data type with the following features:

  • Only a limited number of words (or strings) should be matched
  • All values are pre-defined (before the test)

Then the Choice type is a solution for your problem. Common use cases for the choice type are:

  • text-based enumerations (string enum)
  • color names

Feature Example

Assuming you want to write something like this:

# file:datatype.features/choice.feature
Feature: User-Defined Choice Type

    | The user-defined choice type supports only the following
    | choice of words:  apples, beef, potatoes, pork

    Scenario: Good Case
        Given I go to a shop to buy ingredients for a meal
        When I buy apples
         And I buy beef

Define the Data Type

# file:datatype.features/steps/step_choice.py
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder

# -- CHOICE: Constrain to a list of supported items (as string).
offered_shop_items = [ "apples", "beef", "potatoes", "pork" ]
parse_shop_item = TypeBuilder.make_choice(offered_shop_items)
register_type(ShopItem=parse_shop_item)

Note

The TypeBuilder.make_choice() function performs the magic. It computes a regular expression pattern for the given choice of words/strings and stores them in parse_shop_item.pattern attribute. This optional attribute is used by the parse module to improve pattern matching for user-defined types.

Provide the Step Definitions

# file:datatype.features/steps/step_choice.py
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then

@given(u"I go to a shop to buy ingredients for a meal")
def step_given_I_go_to_a_shop(context):
    context.shopping_cart = [ ]

@when(u"I buy {shop_item:ShopItem}")
def step_when_I_buy(context, shop_item):
    assert shop_item in offered_shop_items
    context.shopping_cart.append(shop_item)

Run the Test

Now we run this example with behave (and all steps are matched):

$ behave --tags=-xfail --no-skipped ../datatype.features/choice.feature
Feature: User-Defined Choice Type   # ../datatype.features/choice.feature:1
  | The user-defined choice type supports only the following
  | choice of words:  apples, beef, potatoes, pork

  Scenario: Good Case                                  # ../datatype.features/choice.feature:6
    Given I go to a shop to buy ingredients for a meal # ../datatype.features/steps/step_choice.py:34
    When I buy apples                                  # ../datatype.features/steps/step_choice.py:38
    And I buy beef                                     # ../datatype.features/steps/step_choice.py:38

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 1 skipped
3 steps passed, 0 failed, 4 skipped, 0 undefined
Took 0m0.001s

SAD Feature Example

The following feature example shows that only supported choice values are matched.

# file:datatype.features/choice.feature
Feature: User-Defined Choice Type
    Scenario: Bad Case -- Undefined step definition for "diamonds"
        Given I go to a shop to buy ingredients for a meal
        When I buy apples
         And I buy pork
         And I buy diamonds

When you run this example with behave the last step is not matched:

$ behave --tags=xfail --no-skipped ../datatype.features/choice.feature
Feature: User-Defined Choice Type   # ../datatype.features/choice.feature:1
  | The user-defined choice type supports only the following
  | choice of words:  apples, beef, potatoes, pork

  @xfail
  Scenario: Bad Case -- Undefined step definition for "diamonds"  # ../datatype.features/choice.feature:12
    Given I go to a shop to buy ingredients for a meal            # ../datatype.features/steps/step_choice.py:34
    When I buy apples                                             # ../datatype.features/steps/step_choice.py:38
    And I buy pork                                                # ../datatype.features/steps/step_choice.py:38
    And I buy diamonds                                            # None


You can implement step definitions for undefined steps with these snippets:

@when(u'I buy diamonds')
def step_impl(context):
    raise NotImplementedError(u'STEP: When I buy diamonds')


Failing scenarios:
  ../datatype.features/choice.feature:12  Bad Case -- Undefined step definition for "diamonds"

0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 1 skipped
3 steps passed, 0 failed, 3 skipped, 1 undefined
Took 0m0.001s

See also

Use Multi-Methods in Step Definitions

For a solution of this problem.

The Complete Picture

# file:datatypes.features/choice.feature
Feature: User-Defined Choice Type

    | The user-defined choice type supports only the following
    | choice of words:  apples, beef, potatoes, pork

    Scenario: Good Case
        Given I go to a shop to buy ingredients for a meal
        When I buy apples
         And I buy beef

    @xfail
    Scenario: Bad Case -- Undefined step definition for "diamonds"
        Given I go to a shop to buy ingredients for a meal
        When I buy apples
         And I buy pork
         And I buy diamonds
# file:datatype.features/steps/step_choice.py
# -*- coding: UTF-8 -*-
"""
Based on ``behave tutorial``

Feature: User-Defined Choice Type (advanced tutorial01: choice)

    | The user-defined choice type supports only the following words:
    |   apples, beef, potatoes, pork

    Scenario:
        Given I go to a shop to buy ingredients for a meal
        And I buy apples
        And I buy beef
"""

# @mark.user_defined_types
# ------------------------------------------------------------------------
# USER-DEFINED TYPES:
# ------------------------------------------------------------------------
from behave import register_type
from parse_type import TypeBuilder

# -- CHOICE: Constrain to a list of supported items (as string).
offered_shop_items = [ "apples", "beef", "potatoes", "pork" ]
parse_shop_item = TypeBuilder.make_choice(offered_shop_items)
register_type(ShopItem=parse_shop_item)

# @mark.steps
# ----------------------------------------------------------------------------
# STEPS:
# ----------------------------------------------------------------------------
from behave import given, when, then

@given(u"I go to a shop to buy ingredients for a meal")
def step_given_I_go_to_a_shop(context):
    context.shopping_cart = [ ]

@when(u"I buy {shop_item:ShopItem}")
def step_when_I_buy(context, shop_item):
    assert shop_item in offered_shop_items
    context.shopping_cart.append(shop_item)