Preflight is a tool for writing unit and integration tests for Taxi projects
It's built in Kotlin, and is a lightweight wrapper around Kotest's DescribeSpec.
The underlying test framework is the same one that the Orbital team uses for the tests in the Orbital codebase.
It supports:
- Compiling Taxi projects
- Testing Taxi queries
- Stubbing data sources
- (Planned): Running against real data sources using Nebula in tests is planned [#2]
Note
Currently, there's more boilerplate here then we'd want -- that's because the Taxi and Orbital libraries aren't available on Maven Central
Currently, Preflight tests are executed using Gradle. We have a gradle plugin that handles most of the wiring.
// build.gradle.kts
plugins {
id("com.orbitalhq.preflight")
}
We plan to provide full support for executing tests using the Taxi CLI, which will make Gradle optional for CI/CD purposes.
However, the Preflight Gradle Plugin offers additional benefits beyond test execution:
- Configures IntelliJ source roots for better IDE integration
- Enables Kotlin code completion when writing tests
- Provides a seamless development experience within existing Gradle projects
Note: Code completion is available for Kotlin test code only. IntelliJ does not currently support code completion for Taxi schema files.
For this reason, we plan to support both approaches:
- Gradle Plugin: Recommended for development due to superior IDE experience
- Taxi CLI: Ideal for lightweight CI/CD pipelines and standalone execution
Tests are written using Kotlin, following Kotest's Describe spec style.
A custom base class of OrbitalSpec
is provided, which provides several convenience functions to make testing easier:
class PersonTest : OrbitalSpec({
describe("Simple tests") {
it("returning a scalar") {
"""find { 1 + 2 }""".queryForScalar()
.shouldBe(3)
}
}
})
By default, the taxi project is compiled before any tests are run. If compilation fails, the tests are not executed.
The OrbitalSpec
base class provides several query helper methods that make testing easier:
All query methods accept a Taxi query string and return the result in different formats:
queryForScalar():Any?
- Returns the first result from the query as a raw scalar values (Int, String, Boolean, etc.)queryForObject():Map<String,Any?>
- Returns the first result from the query as aMap<String,Any?>
queryForCollection():List<Map<String,Any?>>
- Returns a collection of objects/entitiesqueryForTypedInstance():TypedInstance
- (Advanced usage only) Returns Orbital's internalTypedInstance
, which provides access to data sources (including lineage tracing) and error messages. Normally, you'd callqueryForObject()
class PersonTest : OrbitalSpec({
describe("Simple tests") {
it("returning a scalar") {
"""find { 1 + 2 }""".queryForScalar()
.shouldBe(3)
}
it("returning an object") {
"""given { Person = { id: 123, age: 12 } }
find { PossibleAdult }
""".queryForObject()
.shouldBe(
mapOf(
"id" to 123,
"age" to 12,
"isAdult" to false
)
)
}
}
})
Each query method supports stubbing external data sources in two ways:
1. Using Stub Scenarios (Recommended)
it("should let me customize a stub return value") {
"""
find { Person(PersonId == 1) } as PossibleAdult
""".queryForObject(
stub("getPerson").returns("""{ "id" : 123, "age" : 36 }""")
)
.shouldBe(mapOf(
"id" to 123,
"age" to 36,
"isAdult" to true
))
}
2. Using Stub Customizer (Advanced)
it("advanced stub customization") {
"""find { Person(PersonId == 1) }""".queryForObject { stubService ->
// Fine-grained control over stub configuration
stub.addResponse("getPerson", """{ "id" : 123, "age" : 36 }""")
}
}
The stub scenarios approach is more convenient for most use cases, while the stub customizer allows for things like
- controlling responses based on inputs
- streaming responses (for faking Kafka topics, etc)
- Throwing errors / simulating failures
OrbitalSpec
also makes the following accessors available:
taxi
: The low-level Taxi compiled documentschema
: Access to Orbital's Schema instance - which provides a higher-level abstraction over the compiled Taxi documenttaxiProject
: Provides access to the Taxi Package Project - taxi's taxi.conf file, and source roots, etc.
my-project/
├─ src/
│ ├─ person.taxi
├─ test/ <--- tests go here (by convention)
│ ├─ PersonTest.kt
├─ taxi.conf
├─ build.gradle.kts
├─ settings.gradle.kts
Assertions are written using Kotest Assertions
Currently, tests are executed using Gradle
Preflight is a simple wrapper around Kotest.
gradle test
Here's a list of things we plan to cover as part of a 1.0 release
- Publish to Gradle Plugin Portal
- Get all required dependencies (Orbital / Taxi) on Maven Central, to reduce
repository
boilerplate - Support for mixed-source Taxi projects (Avro, Protobuf, OAS, etc)
- Nebula support
- Integration with Taxi CLI (making gradle usage optional)
- Update docs on Orbital docs
- Copy to / from Taxi Playground
- Data driven tests with simple config files