Skip to content

obsidiansystems/daml-cucumber

Repository files navigation

daml-cucumber 🥒

Haskell Programming Language BSD3 License Built with Nix

Behavior-driven-development for Daml script using Cucumber's Gherkin specification language.

daml-cucumber-1.0.0.0.mp4

This repository includes both a Daml library and an executable that reads gherkin .feature files and invokes your Daml test script with the feature as input.

How to use this library

Add daml-cucumber to your project

The daml-cucumber daml library is found in the ./daml folder of this project. You can build and import daml-cucumber-<version>.dar, into your project as one of the data-dependencies in your daml.yaml file.

You can build the daml-cucumber daml library with the following commands:

nix-shell
cd daml
daml build

Implement tests for each cucumber step

Your Daml test suite should import Cucumber, which provides the function liftScript and the Cucumber Action. These can be used to define cucumber scenario implementations. For example, given the following template and feature file:

template X
  with
    owner : Party
  where
    signatory owner
Feature: Example

  Scenario: a contract can be created
    Given a party
    When the party creates contract X
    Then Contract X is created

You can implement each step as a Cucumber action. As long as you annotate the step with a comment that matches the step definition in your feature file, it will be detected and run at the appropriate time.

data Ctx = Ctx with
  party1 : Optional Party

instance Default Ctx where
  def = Ctx with party1 = None

-- Given a party
givenAParty: Cucumber Ctx ()
givenAParty = do
  p <- liftScript $ allocateParty "alice"
  put $ Ctx with party1 = Some p

-- When the party creates contract X
whenThePartyCreatesContact : Cucumber Ctx (ContractId X)
whenThePartyCreatesContact = do
  malice <- gets party1
  case malice of
    Some alice -> liftScript $ submit alice $ createCmd X with owner = alice
    _ -> error "Missing party1"

-- Then Contract X is created
thenContractIsCreated : Cucumber Ctx ()
thenContractIsCreated = do
  malice <- gets party1
  case malice of
    Some alice -> do
      contracts <- liftScript $ query @X alice
      assertMsg "Must have exactly one contract" $ Prelude.length contracts == 1
    _ -> error "Missing party1"

A full project example (using the daml skeleton app) is available in the example folder.

Sharing scenario state

Each scenario has a state or context that is shared by all of the steps that implement that scenario. You can use the functions defined in DA.Action.State.Class to get, put, and modify the scenario state.

Running scenarios with daml-cucumber

Launch daml-cucumber to run tests like so:

daml-cucumber \
  --features <path-to-your-feature-files> \
  --source <path-to-daml-files-implementing-steps>

daml-cucumber will run all of the scenarios in the specified feature file and produce a report in your terminal that looks like the following:

Feature: Example
  Scenario: a contract can be created
    Given a party => Passed
    When the party creates contract X => Passed
    Then Contract X is created => Failed: Not implemented

daml-cucumber will also notify you of missing steps, if the --allow-missing flag is not set, missing steps is an error.

Inspecting test results with VSCode

daml-cucumber generates a daml file that can be opened in VSCode or evaluated with daml test. It is generated whenever daml-cucumber runs, but you can also generate it at any time with the following command:

daml-cucumber
  --features <path-to-your-feature-files> \
  --source <path-to-daml-files-implementing-steps> \
  --generate-only

This will create a file called Generated.daml that contains a function for each scenario in your feature files:

-- | Scenario: a contract can be created
aContractCanBeCreated: Script ()
aContractCanBeCreated = do
  _ <- runCucumber $ do
    givenAParty
    whenThePartyCreatesContact
    thenContractIsCreated
  pure ()

Building daml-cucumber

To build the daml-cucumber executable, run:

cd hs
nix-build

Working on daml-cucumber

From the project root, run nix-shell to get a shell with the daml command, daml sdk, ghci, cabal, and necessary haskell packages installed.

Now you can run the cucumber tests:

nix-shell
cd hs
cabal repl exe:daml-cucumber
:main --source ../test --features ../test/features.feature

Setting up the Nix Binary Cache

To speed up the build process, you can fetch pre-built artifacts from our binary cache.

  1. Install Nix. If you already have Nix installed, make sure you have version 2.0 or higher. To check your current version, run nix-env --version.

  2. Set up nix caches

    1. If you are running NixOS, add this to /etc/nixos/configuration.nix:
      nix.binaryCaches = [ "s3://obsidian-open-source" ];
      nix.binaryCachePublicKeys = [ "obsidian-open-source:KP1UbL7OIibSjFo9/2tiHCYLm/gJMfy8Tim7+7P4o0I=" ];
      and rebuild your NixOS configuration (e.g. sudo nixos-rebuild switch).
    2. If you are using another operating system or Linux distribution, ensure that these lines are present in your Nix configuration file (/etc/nix/nix.conf on most systems; see full list):
      binary-caches = https://cache.nixos.org s3://obsidian-open-source
      binary-cache-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= obsidian-open-source:KP1UbL7OIibSjFo9/2tiHCYLm/gJMfy8Tim7+7P4o0I=
      binary-caches-parallel-connections = 40

Building Docker Containers

To build the docker containers you can run

nix-build -A daml-$sdkversion.container

and you'll get a .tar.gz that you can use

docker load -i $tarfile

to import

to push all the containers you can run

$(nix-build -A pushScript)/bin/docker-push-generated

Built by Obsidian Systems.