Testing
Depot provides a test framework with the capability of verifying that data transformation and data flow are correct. Tests are written as part of the package repository, runnable under any standard test runner (Jest, Vitest, Node test).
Setup
Add @stage-tech/depot-test to your npm dependencies and configure depot.properties with Snowflake credentials. For in-depth documentation go to the README.MD file in the packages/depot-test repository folder of stage-depot.
To run tests in GitHub Actions or AWS CodeBuild you will need to set up secrets if it’s not done yet.
Check you have created an empty Snowflake Database that matches the name of the sql.database
value in your depot.properties file before you run your tests.
The DepotTest object
The main entry point of the framework is the DepotTest class. It provides a single entry point, the in(namespace) method.
You can then access a set of chainable methods to set up your test scenario, move data around, and check results:
| setup | data motion | control |
|---|---|---|
in setContent withAdditionalSchemas named options | merge replace upsert insert patch delete refresh call transaction legacySteps export | check checkExports run |
These methods only accumulate work items for deferred execution, with the exception of run() which takes all
accumulated work items and executes them in a single back-end operation.
Once you've set up your test scenario, you can execute in one of three ways:
- Execute the test in a test runner (Jest, Vitest, Node test). Call
.run()to await the result; failures throw and surface as test failures.
import * as path from "node:path";
import { DepotTest } from "@stage-tech/depot-test";
import * as pkg from "..";
describe("some clever test name", () => {
it("some clever scenario name", async () => {
await DepotTest.in(pkg.mergedSchemas)
.setContent(path.join(__dirname, 'data'))
.check({
expectedUri: path.join(__dirname, 'expected'),
actual: ['my.Result']
})
.run();
});
});
- Execute the test asynchronously at your own pace
import * as path from "node:path";
import { MergedSchemas } from "@stage-tech/depot-schema";
async function checkSomeScenario(namespace: MergedSchemas): Promise<boolean> {
try {
const scenario = DepotTest.in(namespace)
.setData(path.join(__dirname, 'data'))
.check({
expectedUri: path.join(__dirname, 'expected'),
actual: ['my.Result']
});
await scenario.run();
return true;
} catch (e) {
console.warn(e);
return false;
}
}
- Set up the test as multiple given/when/then bits
import * as path from "node:path";
import { MergedSchemas } from "@stage-tech/depot-schema";
const dt = DepotTest
.in(namespace)
.options({ sessionTimeoutMilliseconds: 90_000 });
function givenXXX() {
dt.setData(path.join(__dirname, 'data'));
}
function whenXXX() {
dt.transaction({
source: "my.Source",
target: "my.TransformTarget"
})
}
async function thenXXX() {
dt.check({
expectedUri: path.join(__dirname, 'expected'),
actual: ['my.Result']
});
await dt.run();
}
async function thenYYY() {
dt.check({
expectedUri: path.join(__dirname, 'expected'),
actual: ['my.OtherResult']
});
await dt.run();
}
In this example, we use the "long-running sessions" mode, where sessions linger within the back-end after a .run().
In each then...() step, we invoke .run() to execute the test steps declared so far, and immediately check the results.
We chose here to not do this immediately in the .then() steps, in order to accumulate multiple checks before executing the test
(but we could. It is a tradeoff between performance and precision of error reporting).
Seeded views
The framework supports loading data into non-materialized views. This is done by adding json file to dataURI for a step with qualified view name as file name. It will create View as a table and will load provided data into it.
Use with caution in multistep tests
Programmatic data input/output
The normal operation of DepotTest is to rely on static files (CSV, JSON, etc.) to provide input data and to compare the results of a test with a set of static files containing the expected results.
In some circumstances, this isn't as convenient as having the ability to programmatically provide input data and to obtain output data in order to perform assertions outside DepotTest.
This mode of operation is supported, and described in advanced: programmatic data input/output.
Skipping expressions (emulating replication/rollup)
If your tests are to emulate e.g. views built from replicated schemas with expressions, it is recommended to pass
skipExpressions: true to test steps, this is the behaviour used by the real replication process, and will avoid
UnsupportedOperationExceptions being thrown when expressions are too complex to apply using SQL (and is generally
better to match the real behaviour).
const scenario = DepotTest.in(…)
.setContent({
dataUri: "test/clever/name/data",
skipExpressions: true
})
.…
In legacy mode, this can be passed in the long-hand form of TestSteps or the shorthand form as follows:
const longSteps: TestStep[] = [
{
name: "seed data",
operation: Operation.UPSERT,
dataUri: "test/clever/name/data",
skipExpressions: true
}
];
const shortSteps: TestStep[] = [
objectTestStep("test/clever/name/data", "my step", "my.schema.Name", { skipExpressions: true })
];