---
title: "Test sequences of actions against a simple · Agent Wranglers"
url: https://agentwranglers.se/playbook/testing/unit-tests/stateful-property-tests
updated: 2026-10-21
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Test sequences of actions against a simple model

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Unit tests](https://agentwranglers.se/playbook/testing/unit-tests.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers). Updated 2026-10-21.

- ID: TST-49
- Level: Recommended
- Chapter: [Unit tests](https://agentwranglers.se/playbook/testing/unit-tests)
- Rule: For objects that change over many steps, such as a cart, a balance or a booking calendar, let a tool generate sequences of actions. After each step, compare the object with a simple model.
- How: A plain [property test](https://agentwranglers.se/playbook/testing/unit-tests/property-tests) checks one call. Many bugs need a series of calls, such as add, remove, add again and then pay. List the actions a caller can take. Keep a model that does the same job in the simplest way, such as a dictionary of item counts. The tool runs random sequences against both and checks that they agree after every step. On a failure, it shrinks the sequence to the fewest steps that still fail. Hypothesis calls this a rule-based state machine, and fast-check calls it model-based testing. Copy each shrunk failure into a plain example test.
- Check: Each object on the [risk list](https://agentwranglers.se/playbook/testing/test-strategy/test-by-risk) with three or more actions that change it has a stateful test. Break one action on purpose, such as removing an item twice. The test fails and prints a sequence of at most 10 steps.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### Python example

```python
from hypothesis import strategies as st
from hypothesis.stateful import RuleBasedStateMachine, invariant, rule

SKUS = st.sampled_from(["mug", "shirt", "poster"])

class CartMachine(RuleBasedStateMachine):
    def __init__(self):
        super().__init__()
        self.cart = Cart()
        self.model = {}

    @rule(sku=SKUS, quantity=st.integers(min_value=1, max_value=5))
    def add(self, sku, quantity):
        self.cart.add(sku, quantity)
        self.model[sku] = self.model.get(sku, 0) + quantity

    @rule(sku=SKUS)
    def remove(self, sku):
        self.cart.remove(sku)
        self.model.pop(sku, None)

    @invariant()
    def counts_match_the_model(self):
        assert self.cart.counts() == self.model

TestCart = CartMachine.TestCase
```

### JavaScript example

```js
import fc from 'fast-check';
import { expect, test } from 'vitest';

class AddCommand {
  constructor(sku, quantity) { this.sku = sku; this.quantity = quantity; }
  check() { return true; }
  run(model, cart) {
    cart.add(this.sku, this.quantity);
    model[this.sku] = (model[this.sku] ?? 0) + this.quantity;
    expect(cart.counts()).toEqual(model);
  }
  toString() { return `add(${this.sku}, ${this.quantity})`; }
}

class RemoveCommand {
  constructor(sku) { this.sku = sku; }
  check() { return true; }
  run(model, cart) {
    cart.remove(this.sku);
    delete model[this.sku];
    expect(cart.counts()).toEqual(model);
  }
  toString() { return `remove(${this.sku})`; }
}

const anySku = fc.constantFrom('mug', 'shirt', 'poster');
const cartCommands = [
  fc.tuple(anySku, fc.integer({ min: 1, max: 5 })).map(([sku, quantity]) => new AddCommand(sku, quantity)),
  anySku.map(sku => new RemoveCommand(sku)),
];

test('the cart matches its model after any sequence of steps', () => {
  fc.assert(fc.property(fc.commands(cartCommands), steps => {
    fc.modelRun(() => ({ model: {}, real: new Cart() }), steps);
  }));
});
```

## Sources

- [Hypothesis stateful tests](https://hypothesis.readthedocs.io/en/latest/stateful.html)
- [fast-check model-based testing](https://fast-check.dev/docs/advanced/model-based-testing/)

## Related rules

- [Check rules that hold for any input with property tests](https://agentwranglers.se/playbook/testing/unit-tests/property-tests) in Testing
- [Test what would hurt to break](https://agentwranglers.se/playbook/testing/test-strategy/test-by-risk) in Testing

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/testing/unit-tests/stateful-property-tests">Agent Wranglers testing playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Test rules with several conditions as a table](https://agentwranglers.se/playbook/testing/unit-tests/decision-tables)
- Next: [Test times at the daylight saving changes](https://agentwranglers.se/playbook/testing/unit-tests/daylight-saving-tests)
- [Unit tests](https://agentwranglers.se/playbook/testing/unit-tests)
