Sattl – Salesforce Testing Tool

Automate Your Salesforce Tests at Scale
I’m thrilled to introduce one of my projects that was recently open-sourced.: Sattl (“SAlesforce Testing TooL”), a streamlined CLI designed for defining and executing end-to-end tests on any Salesforce org. When developing integrations or data flows with Salesforce, ensuring that objects are created, validated, and cleaned up in the correct sequence can be a major challenge—especially with multiple interrelated records. Sattl simplifies this process by converting your manifests, asserts, and deletes (written in plain YAML) into reproducible test cases with built-in retries and cleanup.
In this post we’ll walk through:
👉 What Sattl is and why you need it
🛠️ Quick install & CLI primer
⚙️ Core concepts (manifest, assert, delete, steps & cases)
📂 A complete test‑case example
🎯 When & why to use Sattl
🙏 Acknowledgments & inspiration
1. What is Sattl?
Sattl is useful when creating objects in Salesforce triggers the generation of other objects. It helps ensure that everything works as expected by providing a way to test these processes.
Sattl interprets a folder of YAML files as a suite of Salesforce CRUD tests. Under the hood it will:
Upsert records from one or more manifest files
Assert that records exist (and match) via an assert file
Delete records via a delete file
Everything runs in a defined sequence with a built‑in retry/back‑off for flaky Salesforce operations. Point Sattl at a directory and it auto‑groups files into “steps” and then into a “test case,” so you never have to write glue‑code or bespoke test scripts.
2. Quick Install & CLI Primer
Install from PyPI:
pip install -e git+https://github.com/cscetbon/sattl.git#egg=sattl
Or use Docker:
docker pull ghcr.io/cscetbon/sattl:main
Basic usage
Of course you need a test folder to run those commands.
# Run all test cases under a folder, retrying asserts up to 900s:
sattl --sf-org my-org --timeout 900 /path/to/tests/
# Run a single specific test case:
sattl --sf-org my-org --timeout 300 \
--test-case /path/to/tests/00-create-account/
# Or with docker
docker run --rm ghcr.io/cscetbon/sattl:main --sf-org my-org --timeout 900 /path/to/tests/
3. Core Concepts (Jargon)
Term | Description |
Manifest | One or more YAML objects to create or upsert in Salesforce. |
Assert | One or more YAML objects whose fields must exist and match on the Salesforce side (with timeout). |
Delete | One or more YAML objects to delete from your Salesforce org. |
Test Step | A group of manifests, asserts, deletes run in this order: Manifest(s) → Assert → Delete. |
Test Case | A folder of test‑step files grouped/alphabetized by prefix (e.g. 01-… , 02-… ). |
4. A Complete Test‑Case Example
Assume this directory structure:
test-case-01/
├── 00-delete.yaml
├── 01-account.yaml
├── 01-course-and-section.yaml
├── 01-enrollment.yaml
├── 02-assert.yaml
└── 02-delete.yaml (symlink to 00-delete.yaml)
Test Step 00
Delete
# 00-delete.yaml
type: Account
externalID:
Namespace_University_ID__c: 1800008:SC
---
type: Course__c
externalID:
Slug__c: MT-105A-03
# …and so on for Section__c, Enrollment__c, Case, PlatformAccount
What happens?
Sattl deletes any existing records matching those external IDs, cleaning up your org before you start.
Test Step 01
Manifests
# 01-account.yaml
type: Account
externalID:
Namespace_University_ID__c: 1800008:SC
sis_first_name__c: John
sis_last_name__c: Doe
University_Email__c: jdoe@test.com
relations:
recordTypeID:
type: RecordType
name: SIS Student
---
# 01-course-and-section.yaml
type: Course__c
externalID:
Slug__c: MT-105A-03
name: Sample Course
course_code__c: MT-105A-03
course_title__c: Mathematics 1
# …
relations:
course__c:
type: Course__c
slug__c: MT-105A-03
---
# 01-enrollment.yaml
type: Enrollment__c
externalID:
Slug__c: aaa52d9a-520c-4c7e-bbbb-3b6fde10b302:MT-105A-03:HOLDING:2020/1_05
relations:
section__c:
type: Section__c
slug__c: MT-105A-03:HOLDING:2020/1_05
account__c:
type: Account
Namespace_University_ID__c: 1800008:SC
# …additional fields
Upsert logic & relations
Anyrelations:
block tells Sattl to look up the related record by external ID, grab its Salesforce ID, and set that lookup field. If the lookup fails, Sattl errors before attempting the upsert.
Assert
# 02-assert.yaml
type: Enrollment__c
externalID:
Slug__c: aaa52d9a-520c-4c7e-bbbb-3b6fde10b302:MT-105A-03:HOLDING:2020/1_05
first_name__c: John
last_name__c: Doe
Sattl retries the assert block (up to your
--timeout
) and diffs any mismatches.
Final Cleanup
# 02-delete.yaml (same as 00-delete.yaml as it's a symlink)
5. When & Why to Use Sattl
Idempotent test runs – clean up old data before you start.
Clear separation – manifests for setup, asserts for verification, deletes for teardown.
Retries built in – no more flaky tests when Salesforce is slow.
Pure YAML – no custom scripts or glue code required.
CI/CD friendly – run from your pipeline or local Docker container.
6. Acknowledgments & Inspiration
🙏 Thanks to Nicole Dalcin for her contributions to Sattl!
💡 Sattl was inspired by KUTTL, the Kubernetes testing framework for operators.
Getting Started
Clone the repo:
git clone https://github.com/cscetbon/sattl.git cd sattl
Install:
pip install .
Run your tests:
sattl --sf-org your-org-alias \ --timeout 600 \ path/to/your/salesforce-tests/
Happy testing! 🎉
💡 An interesting PR could be to publish Sattl to PYPI.
Full docs & examples on GitHub → cscetbon/sattl
Subscribe to my newsletter
Read articles from Cyril Scetbon directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
