TickSpec

repository·master·Indexed 40 days ago

https://github.com/fsprojects/tickspec

A lightweight Behaviour Driven Development (BDD) framework for .NET that allows developers to describe behavior in Gherkin plain text and execute it against F# or C# step definitions. It supports .NET Standard 2.0, .NET 4.5, and later environments, featuring functional injection, dependency resolution, and support for Gherkin tables and lists.

Tokens
2.2K
Snippets
6
Records
9
Agent score
0%

What's inside TickSpec

  1. How dependency resolution and functional injection work

    master

    TickSpec provides several ways to resolve additional parameters in step methods beyond simple regex captures:

    1. Instance Stashing (Functional Injection): If a step method returns a value, TickSpec stashes that instance under its type. Subsequent steps can request that type as a parameter. Only one value per type is stashed per scenario.
    2. Dependency Resolution: If an instance isn't found in the cache, TickSpec attempts to instantiate the required type using its widest constructor. Arguments for the constructor are resolved recursively. Constructed instances are then cached.
    3. Scenario Metadata: To access scenario-specific info like tags, include a parameter of type ScenarioMetadata in your step method or constructor.

    Lifecycle: Instances are per-scenario. The cache is cleared after each scenario, and any IDisposable instances are automatically disposed.

  2. Define behavior using Gherkin feature specifications

    master

    TickSpec uses Gherkin business language (Given, When, Then) to describe behavior in plain text. These scenarios are then matched against your code's step definitions.

    Feature: Refunded or replaced items should be returned to stock
    
    Scenario: Refunded items should be returned to stock
        Given a customer buys a black jumper
        And I have 3 black jumpers left in stock
        When he returns the jumper for a refund
        Then I should have 4 black jumpers in stock
  3. Use Lists in step definitions

    master

    Bullet lists in Gherkin can be passed to step methods as array types supported by the standard type conversions (e.g., string[]).

    Given the following actors:
        * Keanu Reeves
        * Bruce Willis
    let [<Given>] ``the following actors:`` (actors : string[]) = 
        availableActors <- Set.ofArray actors
  4. Implement step definitions in F#

    master

    In F#, you define steps using the [<Given>], [<When>], and [<Then>] attributes. You can use regex capture groups in the function names to pass arguments to the methods.

    There are two primary patterns:

    1. Using mutable state: Storing state in a mutable variable outside the functions.
    2. Functional injection: Passing the state through the functions as arguments (the return value of one step is passed to the next).
    type StockItem = { Count : int }
    
    // Pattern 1: Using mutable state
    let mutable stockItem = { Count = 0 }
    
    let [<Given>] ``a customer buys a black jumper`` () = ()
    
    let [<Given>] ``I have (.*) black jumpers left in stock`` (n:int) = 
        stockItem <- { stockItem with Count = n }
    
    let [<When>] ``he returns the jumper for a refund`` () = 
        stockItem <- { stockItem with Count = stockItem.Count + 1 }
    
    let [<Then>] ``I should have (.*) black jumpers in stock`` (n:int) = 
        let passed = (stockItem.Count = n)
        Debug.Assert(passed)
    
    // Pattern 2: Functional injection (no mutable state)
    let [<Given>] ``I have (.*) black jumpers left in stock`` (n:int) = 
        { Count = n }
          
    let [<When>] ``he returns the jumper for a refund`` (stockItem:StockItem) = 
        { stockItem with Count = stockItem.Count + 1 }
          
    let [<Then>] ``I should have (.*) black jumpers in stock`` (n:int) (stockItem:StockItem) = 
        let passed = (stockItem.Count = n)
        Debug.Assert(passed)
  5. Implement step definitions in C#

    master

    In C#, use the [Given], [When], and [Then] attributes on methods within a class. Use regex in the attribute values to match Gherkin steps and capture parameters.

    public class StockStepDefinitions
    {
       private StockItem _stockItem;
    
       [Given(@"a customer buys a black jumper")]
       public void GivenACustomerBuysABlackJumper()
       {
       }
    
       [Given(@"I have (.*) black jumpers left in stock")]
       public void GivenIHaveNBlackJumpersLeftInStock(int n)
       {
          _stockItem = new StockItem() { Count = n };
       }
    
       [When(@"he returns the jumper for a refund")]
       public void WhenHeReturnsTheJumperForARefund()
       {
          _stockItem.Count += 1;
       }
    
       [Then(@"I should have (.*) black jumpers in stock")]
       public void ThenIShouldHaveNBlackJumpersInStock(int n)
       {
          Debug.Assert(_stockItem.Count == n);
       }
    }
  6. Install TickSpec via NuGet or Paket

    master

    To use TickSpec in your .NET project, reference it via NuGet or Paket. The library is compatible with any .NET Standard 2.0, .NET 4.5, or later environment.

    # Using NuGet (example)
    nuget install TickSpec
  7. Use Tables in step definitions

    master

    Gherkin tables can be passed to step methods. You can accept them as a Table object or as an array of records/types if the constructor parameters match the table columns.

    ```gherkin
    When a market place has outright orders:
       | Contract | Bid Qty | Bid Price | Offer Price | Offer Qty |
       | V1       | 1       | 9505      |             |           |
    // Using the Table type
    let [<When>] ``a market place has outright orders:`` (table:Table) = 
        outrightOrders <- toOrders table
    
    // Using an array of records
    type OrderRow = { Contract: string; BidQty: string; BidPrice: string; OfferPrice: string; OfferQty: string }
    let [<When>] ``a market place has outright orders:`` (orderRows: OrderRow[]) = 
        outrightOrders <- toOrders orderRows

    Note: The Table parameter must appear after any regex capture parameters and before any functional injection parameters.

  8. Configure a custom type resolver

    master
    For advanced scenarios, you can replace TickSpec's built-in instantiation mechanism with a custom IServiceProvider by setting the StepDefinitions.ServiceProviderFactory property. This factory is called once per scenario to establish an independent resolution context.
  9. Reference: Supported Type Conversions

    master

    TickSpec automatically converts string arguments from Gherkin steps into the declared types of your step method parameters. The following types are supported:

    • Enum types
    • Union types with no parameters
    • Nullable<T> types (where T is convertible from string)
    • Tuple types (where elements are convertible from string)
    • Array types T [] (where T is convertible from string and the input is comma-delimited)
    • Any types supported by System.Convert.ChangeType