Sattl – Salesforce Testing Tool

Cyril ScetbonCyril Scetbon
4 min read

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:

  1. 👉 What Sattl is and why you need it

  2. 🛠️ Quick install & CLI primer

  3. ⚙️ Core concepts (manifest, assert, delete, steps & cases)

  4. 📂 A complete test‑case example

  5. 🎯 When & why to use Sattl

  6. 🙏 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)

TermDescription
ManifestOne or more YAML objects to create or upsert in Salesforce.
AssertOne or more YAML objects whose fields must exist and match on the Salesforce side (with timeout).
DeleteOne or more YAML objects to delete from your Salesforce org.
Test StepA group of manifests, asserts, deletes run in this order: Manifest(s) → Assert → Delete.
Test CaseA 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
Any relations: 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

  1. Clone the repo:

     git clone https://github.com/cscetbon/sattl.git
     cd sattl
    
  2. Install:

     pip install .
    
  3. 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

0
Subscribe to my newsletter

Read articles from Cyril Scetbon directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cyril Scetbon
Cyril Scetbon