Testing a package
A scenario file is how a package states what it promises. Each scenario
gives the facts, asks one question and says what answer is expected; the
runner evaluates the package on those facts and compares. This page walks
through the parts of a .lawtest file, shows a scenario whose expected
answer is “not established”, and runs the whole directory.
Open this package in the playground → — edit the rules and the scenarios of this page and run them in your browser.
The package under test
Section titled “The package under test”language "law.core" version "0.2";package demo.parking version "0.1.0";namespace "urn:law:demo:parking";
entity Applicant;
relation resident(a: Applicant) kind institutional;relation vehicle_registered(a: Applicant) kind institutional;relation permit_eligible(a: Applicant) kind institutional;
const MONTHLY_RATE: Money = 10 EUR;
function permit_fee(months: Integer) -> Money = months * MONTHLY_RATE;
rule PermitEligibility strict { for a: Applicant; when resident(a) and vehicle_registered(a); then permit_eligible(a);}Anatomy of a scenario
Section titled “Anatomy of a scenario”A scenario file begins with the same three header lines as the package it
tests, then any number of test blocks. Each block has three parts.
given { … }— the case: acontextwith the dates the question is asked at, and the facts. Every fact names itsidandorigin.evaluate …;— the question:truth(…),collect …, a term, orpositions().expect …;— one or more expectations about the answer.
test "given, evaluate, expect" { given { context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; } assert resident(entity_ref("urn:demo:parking:ann")) { id "ann-resident"; origin case_input; } assert vehicle_registered(entity_ref("urn:demo:parking:ann")) { id "ann-vehicle"; origin case_input; } } evaluate truth(permit_eligible(entity_ref("urn:demo:parking:ann"))); expect truth_status == TRUE_ONLY; expect evaluation_status == COMPUTED; expect applied(PermitEligibility);}Expectations you will use most:
| Expectation | Checks |
|---|---|
expect truth_status == TRUE_ONLY; |
the answer to a truth question (TRUE_ONLY, FALSE_ONLY, NEITHER, BOTH) |
expect evaluation_status == COMPUTED; |
that the engine could compute at all; other values name what was missing, such as MISSING_POLICY |
expect applied(Rule); / expect not applied(Rule); |
whether a named rule took part in the answer |
expect value == 30 EUR; |
the value of a calculation |
expect collected_count(1); / expect collected(entity_ref("…")); |
the size and members of a collection |
expect position(Duty, ACTIVE); |
the state of a duty, permission or power |
Expecting “not established”
Section titled “Expecting “not established””The most important scenario in any package is the one where the rule must
stay silent. Only residence is known; nothing says Ann has no vehicle, so
the expected answer is NEITHER, and the rule must not be applied. Had
the package quietly treated a missing fact as false, this scenario would
catch it.
test "residence alone is not enough" { given { context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; } assert resident(entity_ref("urn:demo:parking:ann")) { id "ann-resident"; origin case_input; } } evaluate truth(permit_eligible(entity_ref("urn:demo:parking:ann"))); expect truth_status == NEITHER; expect not applied(PermitEligibility);}A value expectation for the fee function:
test "the fee for a year" { given { context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; } } evaluate permit_fee(12); expect value == 120 EUR;}Run it
Section titled “Run it”law engine test tests/scenarios.lawtest --program parking.lawtest PASS: given, evaluate, expecttest PASS: residence alone is not enoughtest PASS: the fee for a yearThe runner exits with code 0 when every scenario passes and 1 when any fails, so the same command works in continuous integration.
Try one change
Section titled “Try one change”Change the rule so that residence alone suffices — delete
and vehicle_registered(a) from its when line — and run the scenarios
again. The first two still describe the old promise, and the second one
fails:
test PASS: given, evaluate, expecttest FAIL: residence alone is not enough truth_status == NEITHER: в документе TRUE_ONLY not applied(urn:law:demo:parking#PermitEligibility): в документе естьtest PASS: the fee for a yearThat is the point of the “not established” scenario: a change that widens a rule is caught by the case that expected silence.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.