Testing DAML using scenarios

DAML has a built-in mechanism for testing templates called scenarios.

Scenarios emulate the ledger. You can specify a linear sequence of actions that various parties take, and these are evaluated in order, according to the same consistency, authorization, and privacy rules as they would be on the sandbox ledger or ledger server. DAML Studio shows you the resulting Transaction graph.

For more on how scenarios work, see the Examples below.

On this page:

Scenario syntax

Scenarios

example =
  scenario do

A scenario emulates the ledger, in order to test that a DAML template or sequence of templates are working as they should.

It consists of a sequence of transactions to be submitted to the ledger (after do), together with success or failure assertions.

Transaction submission

-- Creates an instance of the Payout contract, authorized by "Alice"
submit alice do

The submit function attempts to submit a transaction to the ledger on behalf of a Party.

For example, a transaction could be creating a contract instance on the ledger, or exercising a choice on an existing contract.

Asserting transaction failure

submitMustFail alice do
  exercise payAlice Call

The submitMustFail function asserts that submitting a transaction to the ledger would fail.

This is essentially the same as submit, except that the scenario tests that the action doesn’t work.

Full syntax

For detailed syntax, see Reference: scenarios.

Running scenarios in DAML Studio

When you load a file that includes scenarios into DAML Studio, it displays a “Scenario results” link above the scenario. Click the link to see a representation of the ledger after the scenario has run.

Examples

Simple example

A very simple scenario looks like this:

example =
  scenario do
    -- Creates the party Alice
    alice <- getParty "Alice"
    -- Creates an instance of the Payout contract, authorized by "Alice"
    submit alice do
      create Payout
        -- There’s only one party: "Alice" is both the receiver and giver.
        with receiver = alice; giver = alice

In this example, there is only one transaction, authorized by the party Alice (created using getParty "Alice"). The ledger update is a create, and has to include the arguments for the template (Payout with receiver = alice; giver = alice).

Example with two updates

This example tests a contract that gives both parties an explicit opportunity to agree to their obligations.

example =
  scenario do
    -- Bank of England creates a contract giving Alice the option
    -- to be paid.
    bankOfEngland <- getParty "Bank of England"
    alice <- getParty "Alice"
    payAlice <- submit bankOfEngland do
      create CallablePayout with
        receiver = alice; giver = bankOfEngland

    -- Alice exercises the contract, and receives payment.
    submit alice do
      exercise payAlice Call

In the first transaction of the scenario, party bankOfEngland (created using getParty "Bank of England") creates an instance of the CallablePayout contract with alice as the receiver and bankOfEngland as the giver.

When the contract is submitted to the ledger, it is given a unique contract identifier of type ContractId CallablePayout. payAlice <- assigns that identifier to the variable payAlice.

In the second statement, exercise payAlice Call, is an exercise of the Call choice on the contract instance identified by payAlice. This causes a Payout agreement with her as the receiver to be written to the ledger.

The workflow described by the above scenario models both parties explicitly exercising their rights and accepting their obligations:

  • Party "Bank of England" is assumed to know the definition of the CallablePayout contract template and the consequences of submitting a contract instance to the ledger.
  • Party "Alice" is assumed to know the definition of the contract template, as well as the consequences of exercising the Call choice on it. If "Alice" does not want to receive five pounds, she can simply not exercise that choice.

Example with submitMustFail

Because exercising a contract (by default) archives a contract, once party "Alice" exercises the Call choice, she will be unable to exercise it again.

To test this expectation, use the submitMustFail function:

exampleDoubleCall =
  scenario do
    bankOfEngland <- getParty "Bank of England"
    alice <- getParty "Alice"
    -- Bank of England creates a contract giving Alice the option
    -- to be paid.
    payAlice <- submit bankOfEngland do
      create CallablePayout with
        receiver = alice; giver = bankOfEngland

    -- Alice exercises the contract, and receives payment.
    submit alice do
      exercise payAlice Call

    -- If Alice tries to exercise the contract again, it must
    -- fail.
    submitMustFail alice do
      exercise payAlice Call

When the Call choice is exercised, the contract instance is archived. The fails keyword checks that if 'Alice' submits exercise payAlice Call again, it would fail.