Testing against fakesnow
The FAKESNOW location type runs a Snowflake test against
fakesnow — a local Snowflake emulator built on DuckDB and
SQLGlot — instead of a real Snowflake account. The Depot Test backend starts the container, resolves
its connection details, and hands them to the ordinary Snowflake execution path. Your test does not
change, no Snowflake account is contacted, and no credentials of any kind are needed: no
depot.properties, no private key, no AWS profile.
The obvious use is speed and cost. The less obvious one is that a suite which needs no secrets can run in CI on a fork, or on a laptop that has never been through SSO.
This page is about Depot Test — DepotTest.in(namespace)… with setContent and check steps.
Depot Local can run against fakesnow too, and the same emulator brings the same parity boundaries;
see Depot Local against fakesnow for that surface.
Running a whole suite against fakesnow
Set the environment variable. It becomes the default location type, so every dataset that does not name one goes to fakesnow:
DEPOT_TEST_LOCATION_TYPE=FAKESNOW yarn test
Most tests name no location type and simply take the default, which is why one variable moves a whole suite. The resolution order is:
- the
locationTypea test sets explicitly, throughoptions()or per dataset, DEPOT_TEST_LOCATION_TYPE,SNOWFLAKE.
A test that names its location type keeps it, whichever one it named — including SNOWFLAKE. Asking
for SNOWFLAKE explicitly is as much a choice as asking for AURORA, and the variable exists for
the tests that expressed no preference.
One consequence worth knowing: in a multi-dataset test, naming a location type on some datasets and leaving others to the default can produce a job whose datasets span two backends. All datasets in one test must share a backend, so the run fails with a message naming the datasets that disagree. Either name the location type on all of them or on none.
An unrecognised value throws at job-build time rather than being ignored. A typo that silently ran the whole suite against real Snowflake would be a far more expensive mistake than a startup failure.
Opting in one test at a time
Set the location type the same way you would choose any other:
import { DepotTest, TestLocationType } from "@stage-tech/depot-test";
await DepotTest.in(namespace)
.options({ locationType: TestLocationType.FAKESNOW })
.setContent({ data: { "petstore.dogs": dogs } })
.check({ data: [{ scope: { schema: "petstore.AnimalCount" }, values: [{ animal: "dog", count: 2 }] }] })
.run();
Or per dataset, in the multi-dataset form. As with the other location types, all
datasets in one test must share a backend — and FAKESNOW counts as its own, so it cannot be mixed
with real SNOWFLAKE either. They reach different endpoints, and a job gets one connection.
Naming FAKESNOW on the test needs no environment variable and no credentials: a test whose datasets
all resolve to FAKESNOW skips the credentials lookup entirely, so there is nothing to configure and
no depot.properties to find.
What works, and what does not
Everything Depot does over the Snowflake SQL API and JDBC works: schema and view deployment,
setContent, transactions, queries, migrations, UDFs, content and count checks. Stages work too —
fakesnow serves them from inside its own container, so PUT into an internal stage and seeding a
test from a staged file behave as they do against Snowflake.
What does not work is anything that needs a real bucket:
- Export steps (testing exports) fail fast with an explanation. There is no S3 behind the stage URL to export into.
s3://stage URLs used as actual S3 locations, for the same reason. A schema may still declare ans3://URL; it is simply never treated as a bucket, and the stage is created inside the container.
This is a deliberate boundary rather than a missing feature: the FAKESNOW path holds no AWS
credentials at all, which is much of its value. A test that genuinely exercises export belongs on
SNOWFLAKE.
Separately, fakesnow implements no stored procedures, so a CALL step comes back as
Catalog Error: Table Function with name ... does not exist. Those tests belong on SNOWFLAKE too.
Snowflake's proprietary HASH values differ as well, since fakesnow computes DuckDB's hash. A test
that asserts a recorded Snowflake hash belongs on SNOWFLAKE.
SNOWFLAKE_MOCK_ICEBERG has no fakesnow flavour.
Requirements
Docker, and nothing else. The backend pulls a pinned image from a public registry and runs a pool of fakesnow instances, one container each. Inside an instance, it isolates concurrent tests from each other by schema prefix in a single database, as it does on real Snowflake.
To pull the images before the tests, use @stage-tech/depot-test/global-setup-docker-fakesnow as the
global setup of the test runner, or depot-test-pull-docker-images fakesnow in a CI step; see
Pull the Docker images before the tests.
The number of instances defaults to auto, which sizes the pool to the machine: one instance per CPU
less one, and no more than the memory available to Docker divided by the memory budget of one
instance. To set a fixed count, set DEPOT_TEST_FAKESNOW_INSTANCES to a positive integer. The backend's
ops limits do not bound fakesnow jobs; they queue at their emulator.
An instance starts only when the backend routes a test to it. All datasets of one test go to the same
instance, and so do all jobs that share a schema prefix. An idle instance stops after 30 seconds
(DEPOT_TEST_FAKESNOW_IDLE_DURATION_SECONDS), unless a long-running session still uses it or it holds
a kept schema (KEEP_SNOWFLAKE_SCHEMA or SNOWFLAKE_SCHEMA_PREFIX).
Each instance runs under a memory limit, DEPOT_FAKESNOW_MEMORY: a whole number of bytes, or a whole
number with a unit k, m or g (binary), such as 2g or 1536m. DuckDB uses about 70% of it,
and a query that needs more spills to disk instead of failing. When the variable is unset, the backend
picks a budget between 1 GiB and 4 GiB from the memory available to Docker.
The limit is a hard cap: an instance that needs more memory than its limit is killed. The tests on it
then fail on a lost connection, not on an out-of-memory error, and docker inspect shows
"OOMKilled": true for its container. Set a larger DEPOT_FAKESNOW_MEMORY if that happens.
Like the other backend variables, these three take effect only when the run starts the backend. A run that finds a backend already running uses the settings of that backend.
Each container's host port is assigned by Docker rather than fixed, so several backends can run against one Docker daemon without colliding.
Using a locally-built fakesnow image
If you are changing fakesnow itself, point the backend at your own build:
DEPOT_FAKESNOW_IMAGE=my-fakesnow:dev DEPOT_TEST_LOCATION_TYPE=FAKESNOW yarn test
Depot needs the Snowflake SQL API v2 (POST /api/v2/statements), which upstream fakesnow does not
implement — so the published image carries that support and an arbitrary ghcr.io/tekumara/fakesnow
tag will not work. Building and publishing the image happens in the fakesnow repository, not in Depot.
Pointing at a fakesnow you manage yourself
FAKESNOW exists so you do not have to run anything by hand. If you nonetheless want to — to attach
a debugger to the emulator, to keep one instance warm across many runs, or to inspect its state
between tests — start fakesnow yourself and use the
sql.endpointOverride escape hatch
instead of this location type.
Either way, one check does not run: Depot normally compares each view's stored DDL against the text it
submitted, and only Snowflake echoes that text verbatim. fakesnow reports the definition after a
round trip through SQLGlot — CREATE VIEW for create or replace view, identifiers re-cased, aliases
and parens added — so the comparison is skipped against any endpointOverride, managed or not. Your
CONTENTS checks are unaffected; they go over the SQL API and compare data.