arnapou/behat

Library - Tooling for behat.

v1.4 2024-08-29 18:47 UTC

This package is auto-updated.

Last update: 2024-09-08 14:13:21 UTC


README

pipeline coverage

Application config system.

Installation

composer require arnapou/behat

packagist 👉️ arnapou/behat

Introduction

The library mainly provides a BehatTool object you can use in your context to process the data.

It takes advantage of arnapou/appcfg to "compile" arrays with contexts.

This allows us to easily use the appcfg syntax to process data to test matching etc ...

Syntax %<processor>(<label>:<expression>)%

  • processor: a way to interpret the label + expression (only a-zA-Z0-9_ characters)
  • label: whatever it means for processor, can contain any character (only the : needs to be escaped like \:)
  • expression (optional): it means for processor, can contain any character (only the : needs to be escaped like \:)

Valid expressions:

  • %<processor>(<label>)% : missing expression + colon :, the expression has "no value".
  • %<processor>(<label>:%<nested_processor>(<nested_label>:<nested_expression>)%)% : you can have several levels of nested expressions.

More details in the arnapou/appcfg library.

Behat sugar

The BehatTool class contains a bunch of methods to help you with the manipulation of compiled arrays with appcfg.

In order to help matching tests, we added an interface MatchingProcessor you can implement and inject through the ProcessorsProvider interface.

The code is properly isolated. Thus, you can use our gherkin helper classes directly in your behat contexts :

  • ArrayHelper : help to fold/unfold arrays + other helpers.
  • TableNodeWithLayout : facade for behat TableNode, in order to keep the behaviour, format and typing consistent.

To ease testing, the lib contains simple implementations of some PSR:

Processors

Default

  • %(array.context.path.value)%
  • %(array.context.path.value:<default>)%

Environment variable

  • %env(VARIABLE)%
  • %env(VARIABLE:<default>)%

Basic cast for static values (shorter syntax alternative)

  • %int(<value>)% or %int(:<value>)%
  • %float(<value>)% or %float(:<value>)%
  • %bool(<value>)% or %bool(:<value>)%
  • %null()%

Filter: basic cast

  • %filter(int:<value>)% or nullable %filter(?int:<value>)%
  • %filter(float:<value>)% or nullable %filter(?float:<value>)%
  • %filter(bool:<value>)% or nullable %filter(?bool:<value>)%
  • %filter(string:<value>)% or nullable %filter(?string:<value>)%

Filter: string functions

  • %filter(md5:<value>)%
  • %filter(sha1:<value>)%
  • %filter(capitalize:<value>)%
  • %filter(lower:<value>)%
  • %filter(upper:<value>)%

Filter: string+array functions

  • %filter(length:<value>)%

Json manipulation

  • %json(decode:<value>)%
  • %json(encode:<value>)%

Date manipulation

  • %date(<format>:<value>)%
  • %date(Y-m-d\TH\:i\:sP:<value>)% (escaped colon in format)
  • %date(Y-m-d:10 june next year)%
  • %date(Y-m-d:1718990615)%

Cache manipulation

  • %save(<cache_key>:<value_to_save>)% save value
  • %cache(<cache_key>:<default_value>)% retrieve value

Value pass through if valid, else null

  • %regex(<pattern>:<value>)%: for any string, example %regex(/^\d+$/)%
  • %between(<min>,<max>:<value>)%: only for numerics, example %between(10,20)%

Matching processors

  • %regex(<pattern>)%: for any string, example %regex(/^\d+$/)%
  • %between(<min>,<max>)%: only for numerics, example %between(10,20)%
  • %undefined(array-key)%: only for array or object properties, example %undefined()%

examples.feature

This is an example, that means that the FeatureContext from the feature folder exists only for this example to be run on the CI.

You may copy code or take ideas from it, it's up to you.

Feature: Examples of what we can achieve

  Background:
    # For the example, this context is also interpreted and compiled.

    # In a real scenario, for example, these context data would probably
    # come from the last json response of a http query.
    Given a common JSON used as context for all compilation examples
    """
    {
      "items": [1, 2, 3, 4, 5, 6, 7, 8, 9],
      "text": "Hello World",
      "date": "%int(:%date(U:2024-06-23T19:03:48+00:00)%)%",
      "user": { "name": "John", "age": 20 }
    }
    """

  Scenario: "Rows" layout

    # The compilation is done with the context of the background.
    When we compile these "rows"
      | column A                       | column B        | column C                 |
      | foo                            | %items.1%       | %(date)%                 |
      | %save(username:%(user.name)%)% | %(items.2)%     | %filter(upper:%(text)%)% |

    # Basic EQUAL comparison against the previous result
    Then the last compiled is equal to these "rows"
      | column A                       | column B        | column C                 |
      | foo                            | %int(2)%        | %int(1719169428)%        |
      | John                           | %int(3)%        | HELLO WORLD              |

    # Equivalent EQUAL comparison but with raw JSON (not compiled)
    And the last compiled is equal to this JSON
    """
    [{
      "column A": "foo",
      "column B": 2,
      "column C": 1719169428
    },{
      "column A": "John",
      "column B": 3,
      "column C": "HELLO WORLD"
    }]
    """

  Scenario: "Columns" layout with only 1 column

    # The compilation is done with the context of the background.
    When we compile these "columns"
      | row      | foo                            |
      | json     | %json(encode:%(user)%)%        |
      | name     | %cache(username)%              |
      | company  | %cache(user.company:unknown)%  |

    # Basic EQUAL comparison against the previous result
    Then the last compiled is equal to these "columns"
      | row      | foo                            |
      | json     | {"name":"John","age":20}       |
      | name     | John                           |
      | company  | unknown                        |

    # Equivalent EQUAL comparison but with raw JSON (not compiled)
    And the last compiled is equal to this JSON
    """
    {
      "row": "foo",
      "json": "{\"name\":\"John\",\"age\":20}",
      "name": "John",
      "company": "unknown"
    }
    """

  Scenario: "Columns" layout with multiple columns

    # The compilation is done with the context of the background.
    When we compile these "columns"
      | name     | John       | Sue        |
      | age      | %int(20)%  | %int(23)%  |
      | company  | Google     | Microsoft  |

    # Equivalent EQUAL comparison but with raw JSON (not compiled)
    And the last compiled is equal to this JSON
    """
    [{
      "name": "John",
      "age": 20,
      "company": "Google"
    },{
      "name": "Sue",
      "age": 23,
      "company": "Microsoft"
    }]
    """

  Scenario: Matching example

    # The table is "unfolded" before compilation, that does the contrary
    # of flatten, expanding the paths to keys and sub-keys, ...
    When we unfold and compile these "columns"
      | users.0.name      | %cache(username)%                |
      | users.0.age       | %(user.age)%                     |
      | users.1.name      | Sue                              |
      | users.1.age       | %int(21)%                        |
      | items.values      | %(items)%                        |
      | items.count       | %filter(length:%(items)%)%       |
      | date              | %date(Y-m-d:%(date)%)%           |

    # MATCHING comparison which is OK if it does not trigger an exception.
    # You may use either raw values, or matching processors like regex & between.
    Then the last compiled is matching these "columns"
      | users.0.age       | %regex(/^\d+$/)%                 |
      | users.1.age       | %between(20,22)%                 |
      | users.2           | %undefined(age)%                 |
      | users             | %undefined(2)%                   |
      | date              | 2024-06-23                       |
      | unknown           | %undefined()%                    |

    # EQUAL comparison with raw JSON (not compiled)
    And the last compiled is equal to this JSON
    """
    {
      "users": [
        { "name": "John" , "age": 20 },
        { "name": "Sue"  , "age": 21 }
      ],
      "items": {
        "values": [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
        "count": 9
      },
      "date": "2024-06-23"
    }
    """

Php versions

DateRef8.3
23/06/20241.x, main×