GeoEngine workers can do almost anything: read rasters, create vectors, write folders of outputs, run analysis, transform files, or generate reports. That flexibility is powerful, but it also makes testing tricky.Documentation Index
Fetch the complete documentation index at: https://docs.nikaplanet.com/llms.txt
Use this file to discover all available pages before exploring further.
geoengine test gives you a structured way to test open-ended workers. Instead of only checking exact file matches, you describe small test cases with inputs and expectations: the worker should run successfully, create the right output files, and, whenever possible, match expected semantic output.
Use
geoengine test during development, before a production geoengine apply and geoengine push. It helps you catch broken inputs, missing dependencies, incorrect output paths, and accidental behavior changes earlier.What geoengine test Does
A GeoEngine test case runs your worker through the same runtime path as:
geoengine.yaml command contract, and GeoEngine’s normal file/folder mounting behavior.
A test can check things like:
- The worker exits with code
0 - An output file was created
- An output file has a minimum size
- A folder contains a certain number of outputs
- A file hash matches exactly
- Text output matches with normalized whitespace
- JSON or GeoJSON matches with numeric tolerance
Test Folder Layout
When you rungeoengine init, GeoEngine can scaffold a tests/ folder for you.
The scaffolded layout is:
| Path | Purpose |
|---|---|
tests/geoengine.test.yaml | The test manifest. This is where test cases are declared. |
tests/fixtures/ | Small input files used by tests. |
tests/expected/ | Expected output files, if you need comparisons. |
tests/validators/ | Optional project-side helper scripts if your team wants them. geoengine init does not create this folder, and the core geoengine test command does not run these automatically. |
.geoengine-test/ as a fixture folder. It is a temporary output area created by geoengine test.
Path Resolution Rules
geoengine test resolves paths differently depending on whether the input is read-only or writable.
Read-only file and folder inputs
For normal file/folder inputs, relative paths are resolved fromtests/.
Output file and folder inputs
For inputs marked withoutput: true in geoengine.yaml, relative paths are resolved inside the per-case test output folder.
square-grid, this points to:
Manifest Assertions
Each case intests/geoengine.test.yaml can include inputs, extra raw args, and an expect block.
File expectations
| Field | Description |
|---|---|
path | One file or folder to check. It can be an output input name, such as output, or a path relative to the case output folder. |
glob | Match files under the case output folder. Supports *, ?, and **. |
exists | Defaults to true. Set to false when a path should not exist. |
missing | Set to true when a path should be absent. |
min_size / max_size | Check file size in bytes. |
count | With glob, assert the number of matches. |
sha256 | Require an exact SHA-256 hash. Use only for deterministic bytes. |
extension | Require a file extension, such as .geojson or .csv. |
Comparisons
| Field | Description |
|---|---|
actual | Output file path, resolved from the case output folder unless it names an input path. |
expected | Expected file path, resolved from tests/. |
mode | bytes or exact for byte comparison, text for text, json for JSON, geojson for GeoJSON. Defaults to bytes. |
normalize_whitespace | For text mode, compare collapsed whitespace instead of exact spacing. |
tolerance.numbers | Numeric tolerance for json or geojson comparisons. |
tolerance.coordinates | Alias for numeric tolerance in geospatial comparisons. |
ignore_paths | Dot-style JSON paths to ignore, such as $.name or $.features.0.properties.id. |
Start With the Worker Contract
Before writing tests, inspectgeoengine.yaml and answer:
- Which inputs are required?
- Which inputs are output paths?
- Which branches are meaningful to test?
- Which outputs are deterministic enough to compare?
- Which metadata might vary between machines or library versions?
output-dir and uses output-file as a basename, the test should use those same inputs instead of assuming a hard-coded path inside the container.
Smoke Tests vs Semantic Tests
Smoke tests and semantic tests are both useful, but they are not the same.Smoke test
A smoke test proves that the worker runs and writes something plausible:- The worker crashes
- A dependency is missing
- The output path is wrong
- Nothing was written
Tight semantic test
A semantic test checks the meaning of the output:When the output is predictable, prefer semantic tests. Smoke-only tests should be the fallback floor, not the default finish line.
Example: Testing a Grid Creator Worker
Suppose your worker creates regular grids from an input vector extent. The relevantgeoengine.yaml inputs might look like this:
tests/geoengine.test.yaml:
- A square grid with only
cell-width - A rectangle grid with both
cell-widthandcell-height
name, crs, and generated feature id fields.
Running Tests
Before running tests, lint and apply the worker in development mode:.geoengine-test/ output folders for inspection:
Recommended Development Loop
Use this loop while building or changing a worker:| Failure | Usually check |
|---|---|
geoengine lint fails | geoengine.yaml structure or input definitions |
geoengine apply --dev fails | Dependencies, Docker build, script path, config |
geoengine test exits non-zero | Script logic, missing dependencies, bad test inputs |
| Output file missing | Output path, output: true, script write location |
| Comparison fails | Expected file, tolerance, output format, real behavior change |
Choosing the Right Assertion Strictness
Start with the strongest check that matches the stability of the output.Smoke test
Use this for highly open-ended outputs, early scaffolding, or as a baseline case:Tight semantic test
Use this when the expected result can be described:Exact hash test
Use hashes only when output is truly stable:Good Practices
Prefer tight semantic checks
A good test should prove the worker produced the right result, not merely that it produced a file. For deterministic outputs, prefer checks like:- Expected feature count
- Expected properties or columns
- Expected geometry type
- Expected filenames
- Expected CRS behavior, when stable
- Expected numeric values with tolerance
mode: geojson, rather than only checking that output.geojson exists.
Avoid brittle serialization details
Some output fields depend on the writer library, driver version, or machine environment. Ignore these when they are not part of the worker contract. Common fields to ignore:Keep fixtures small
Use the smallest fixture that exercises the behavior. A tiny GeoJSON polygon, a few CSV rows, or a small raster tile is usually better than a realistic production-sized file. Small fixtures make tests:- Faster
- Easier to review
- Easier to debug
- Safer to commit
Test representative branches
Do not write ten nearly identical smoke tests. Instead, choose cases that cover meaningful behavior changes. For example, a grid worker might test:squarerectanglediamond- Optional
add-id: false - Auto-reprojection on/off, if that behavior matters
Use exact hashes sparingly
Hashes are useful only when the entire file is deterministic. Avoid hashes for GeoJSON, rasters, reports, or files with timestamps unless you are certain the serialization is stable. Prefer structured comparison with tolerance when possible.Commit test inputs and expected outputs
Commit:Writing Useful Fixtures
Keep fixtures small and intentional. Good fixtures are:- Tiny enough to commit to Git
- Representative of real user input
- Focused on one behavior
- Stable across machines
- Free of private or customer data
- Huge
- Downloaded from the network during tests
- Machine-specific
- Licensed or sensitive
- More complicated than the behavior being tested
Working With AI Agents
If you are using an AI coding assistant with GeoEngine skills installed, ask it to create tests after the worker config is wired up. Example prompt:tests/geoengine.test.yaml- At least one small fixture
- Expected outputs when the result is predictable
- Assertions that match the worker’s real outputs
- No large generated output committed as a fixture unless it is intentional
What to Commit
For a normal worker repository, commit:*.py with *.R.
Do not commit:
Common Mistakes
The test passes locally but fails for a teammate
Make suregeoengine.lock is committed so everyone is applying and testing the same worker ID.
Also make sure fixtures use relative paths under tests/, not absolute paths from your machine.
The worker writes no output
Check that the correspondinggeoengine.yaml input is marked as an output:
output: true is missing, GeoEngine treats the path as read-only input instead of a writable output mount.
The test is too loose
If your test only checks that a file exists, ask whether the output is predictable enough to compare. For deterministic workers, add an expected output and usemode: json, mode: geojson, or normalized text comparison.
The test is too brittle
If a comparison fails because of metadata, writer-specific fields, timestamps, generated IDs, or tiny floating-point differences, ignore or tolerate those fields instead of weakening the whole test. For example:Tests force image rebuilds
tests/ and .geoengine-test/ are ignored by GeoEngine’s Docker build context. If changing tests appears to trigger rebuilds, update GeoEngine and run:
Summary
Usegeoengine test to make worker development repeatable: