Context
openfn compile now writes compiled job expressions to disk, which means job code can finally be imported into a normal JavaScript test runner. Job expressions are not valid JS out of the box (top-level adaptor calls like get('/endpoint') break
the import), so until now there was no first-class way to unit test the helper functions users write inside their jobs.
There's an internal guide in kit covering the mechanics:
https://github.com/OpenFn/kit/blob/main/.claude/unit-testing-jobs.md
That guide is written for engineers who already know the compiler. We need a user-facing version on docs.openfn.org that is task-oriented: "here is how you test your job code", not "here is what --exports-only does".
Problem
Users writing non-trivial transformation logic (date formatting, SMS parsing, identifier mapping, code lookups) currently have no documented way to test it except by running the whole workflow with the CLI and eyeballing output.json. That's slow, doesn't isolate failures, and doesn't run in CI.
Proposed change
Add a new page: Writing unit tests for your jobs
Suggested location: alongside the job-writing guide, e.g. documentation/jobs/unit-testing-jobs, so it sits with the other "how to write good job code" content rather than buried in the CLI reference.
Cross-links needed from:
documentation/jobs/job-writing-guide — a short "your helpers can be unit tested" pointer
documentation/cli-usage — add compile to the common-usage examples with a link here
documentation/cli — mention testing in the "you can use the CLI to…" list
Content outline
- Why unit test job code — what is and isn't testable. Operations
(fn, get, each) are not unit-testable; pure helper functions are. Set
expectations up front.
- Write your helpers so they're testable — the most important section, and
the one missing from the kit guide. In --exports-only mode only
export const and export function declarations survive; a plain
const formatDate = ... is dropped. So the guidance is: export any helper
you want to test. Include a before/after snippet.
- Compile your workflows —
openfn compile --exports-only, what lands in
dist/, the .mjs extension (and why that means you don't need
"type": "module"), and -o / --clean / --workspace.
- Write a test — a full worked example, source → compiled → test, using
Node's built-in test runner (node --test). Reuse the formatDate example
from the kit guide or write a slightly richer one (e.g. parsing an SMS string
into a structured record) so it demonstrates real value.
- The edit → test loop —
--watch alongside the test runner's watch mode.
- Wire it into your project —
package.json scripts, .gitignore the
output dir, and a short note on running this in CI (GitHub Actions snippet
would be a nice-to-have, not blocking).
- Reference table —
compile flags: --exports-only, -o, -O,
--watch, --clean, --workspace, plus the dirs.compiled key in
openfn.yaml.
- Troubleshooting — see below; this is where most of the new writing is.
Gotchas that must be covered
These are the things that will generate support questions if we skip them:
- "My helper isn't in the compiled file." It wasn't exported. Non-exported
declarations are always dropped in strip mode.
- "No file was written for my step." Steps whose compiled output is empty
after stripping are skipped entirely, so the import fails with a
module-not-found error rather than anything descriptive.
- Adaptor imports are preserved in compiled output. A step that does
import { dateFns } from '@openfn/language-dhis2' will still carry that import
after compilation, so the adaptor package has to be installed locally for the
test to run. This needs an explicit "install your adaptors as devDependencies"
instruction, or a documented pattern for keeping testable helpers free of
adaptor imports. This is the biggest open gap in the current guide.
export default is removed in strip mode — expected, since it only exists
for the runtime.
- Import paths in tests point at the compiled output (
../dist/...), not the
source. Worth stating plainly, and worth noting that the compiled dir should be
gitignored so tests depend on a build step.
- Full compilation (no
--exports-only) writes every step and keeps operations
in export default [...]. Mention it briefly so users know the flag matters,
but don't make it the focus.
Acceptance criteria
Out of scope
- Integration/end-to-end testing of whole workflows (running with fixture state,
asserting on final state). Worth a follow-up page; keep this one focused on
unit tests.
- Mocking adaptor operations or HTTP calls.
- Recommending a specific third-party test runner. Use
node --test in examples
and note that any runner works.
Open questions
- Which
@openfn/cli version first shipped compile with --exports-only? Needs
confirming against the kit release notes / changelog before the page states a
minimum version — I don't want to guess at that.
- Does the compiled output dir get a
.gitignore automatically (as .cli-cache
does), or does the user need to add it? Affects the "wire it into your project"
section.
- Should
compile also get a proper entry in the CLI reference docs as part of
this issue, or as a separate one?
- Is there an existing testing/quality section in the docs IA this should live
under, rather than a standalone page?
Source material
Context
openfn compilenow writes compiled job expressions to disk, which means job code can finally be imported into a normal JavaScript test runner. Job expressions are not valid JS out of the box (top-level adaptor calls likeget('/endpoint')breakthe import), so until now there was no first-class way to unit test the helper functions users write inside their jobs.
There's an internal guide in kit covering the mechanics:
https://github.com/OpenFn/kit/blob/main/.claude/unit-testing-jobs.md
That guide is written for engineers who already know the compiler. We need a user-facing version on docs.openfn.org that is task-oriented: "here is how you test your job code", not "here is what
--exports-onlydoes".Problem
Users writing non-trivial transformation logic (date formatting, SMS parsing, identifier mapping, code lookups) currently have no documented way to test it except by running the whole workflow with the CLI and eyeballing
output.json. That's slow, doesn't isolate failures, and doesn't run in CI.Proposed change
Add a new page: Writing unit tests for your jobs
Suggested location: alongside the job-writing guide, e.g.
documentation/jobs/unit-testing-jobs, so it sits with the other "how to write good job code" content rather than buried in the CLI reference.Cross-links needed from:
documentation/jobs/job-writing-guide— a short "your helpers can be unit tested" pointerdocumentation/cli-usage— addcompileto the common-usage examples with a link heredocumentation/cli— mention testing in the "you can use the CLI to…" listContent outline
(
fn,get,each) are not unit-testable; pure helper functions are. Setexpectations up front.
the one missing from the kit guide. In
--exports-onlymode onlyexport constandexport functiondeclarations survive; a plainconst formatDate = ...is dropped. So the guidance is: export any helperyou want to test. Include a before/after snippet.
openfn compile --exports-only, what lands indist/, the.mjsextension (and why that means you don't need"type": "module"), and-o/--clean/--workspace.Node's built-in test runner (
node --test). Reuse theformatDateexamplefrom the kit guide or write a slightly richer one (e.g. parsing an SMS string
into a structured record) so it demonstrates real value.
--watchalongside the test runner's watch mode.package.jsonscripts,.gitignoretheoutput dir, and a short note on running this in CI (GitHub Actions snippet
would be a nice-to-have, not blocking).
compileflags:--exports-only,-o,-O,--watch,--clean,--workspace, plus thedirs.compiledkey inopenfn.yaml.Gotchas that must be covered
These are the things that will generate support questions if we skip them:
declarations are always dropped in strip mode.
after stripping are skipped entirely, so the import fails with a
module-not-found error rather than anything descriptive.
import { dateFns } from '@openfn/language-dhis2'will still carry that importafter compilation, so the adaptor package has to be installed locally for the
test to run. This needs an explicit "install your adaptors as devDependencies"
instruction, or a documented pattern for keeping testable helpers free of
adaptor imports. This is the biggest open gap in the current guide.
export defaultis removed in strip mode — expected, since it only existsfor the runtime.
../dist/...), not thesource. Worth stating plainly, and worth noting that the compiled dir should be
gitignored so tests depend on a build step.
--exports-only) writes every step and keeps operationsin
export default [...]. Mention it briefly so users know the flag matters,but don't make it the focus.
Acceptance criteria
documentation/and added to the sidebar@openfn/cliand produce the documented output@openfn/cliversion stated on the pageOut of scope
asserting on final state). Worth a follow-up page; keep this one focused on
unit tests.
node --testin examplesand note that any runner works.
Open questions
@openfn/cliversion first shippedcompilewith--exports-only? Needsconfirming against the kit release notes / changelog before the page states a
minimum version — I don't want to guess at that.
.gitignoreautomatically (as.cli-cachedoes), or does the user need to add it? Affects the "wire it into your project"
section.
compilealso get a proper entry in the CLI reference docs as part ofthis issue, or as a separate one?
under, rather than a standalone page?
Source material