Skip to main content
Version: v1.11-alpha

Testing Definitions

defkit ships two testing surfaces that let you verify definitions without deploying to a cluster. Unit tests use defkit.TestContext() and comp.Render() to resolve parameters and context references in-process — no Kubernetes required. E2E tests apply real Application manifests against a live KubeVela cluster and poll for readiness. Start with unit tests for fast feedback on template logic, parameter wiring, and CUE shape; add E2E tests when you need to verify webhook admission, health evaluation, or cross-component interactions.

defkit.TestContext() Builder

defkit.TestContext() returns a *TestContextBuilder that supplies mock runtime values — component name, namespace, parameters, cluster version, and simulated output status — to comp.Render() and comp.RenderAll(). Defaults: name="test-component", namespace="default", appName="test-app", appRevision="test-app-v1", clusterMajor=1, clusterMinor=28.

MethodDescription
defkit.TestContext() *TestContextBuilderCreates a new test context builder pre-loaded with sensible defaults. Call builder methods below to override individual fields.
.WithName(name string) *TestContextBuilderSets context.name — the component or trait name resolved by defkit.VelaCtx().Name() in the template.
.WithNamespace(ns string) *TestContextBuilderSets context.namespace.
.WithAppName(name string) *TestContextBuilderSets context.appName. Use when the template references the application name.
.WithAppRevision(rev string) *TestContextBuilderSets context.appRevision. Use when the template uses revision labels.
.WithParam(name string, value any) *TestContextBuilderSets a single parameter value. Parameters not set here produce a nil value when resolved by comp.Render().
.WithParams(params map[string]any) *TestContextBuilderSets multiple parameter values at once from a map.
.WithClusterVersion(major, minor int) *TestContextBuilderSets the Kubernetes cluster version. Use for templates with defkit.Lt(vela.ClusterVersion().Minor(), ...) version guards.
.WithOutputStatus(status map[string]any) *TestContextBuilderSets the simulated primary output status. Use for testing health policies or status expressions that read context.output.status.
.WithOutputsStatus(name string, status map[string]any) *TestContextBuilderSets status for a named auxiliary output. Use when the template reads context.outputs.<name>.status.
.WithWorkload(workload *defkit.Resource) *TestContextBuilderSets the workload resource for trait testing. Traits read the workload via context.output in the patch block.
.Build() *TestRuntimeContextConstructs the immutable runtime context. Called internally by Render() and RenderAll(); call it directly only when you need to inspect context fields in a test assertion.

comp.Render() and comp.RenderAll()

Render executes the component template in-process with a fully resolved test context. All parameter references, context.name, context.namespace, SetIf conditions, and If/EndIf blocks are evaluated against the values supplied in the TestContextBuilder.

MethodDescription
comp.Render(ctx *TestContextBuilder) *RenderedResourceExecutes the template and returns the primary output resource with all values resolved. Returns nil if the template sets no primary output.
comp.RenderAll(ctx *TestContextBuilder) *RenderedOutputsExecutes the template and returns a *RenderedOutputs containing Primary *RenderedResource and Auxiliary map[string]*RenderedResource. Auxiliary resources gated by tpl.OutputsIf(cond, ...) are included only when the condition evaluates to true against the test context.

*RenderedResource Accessors

MethodDescription
.APIVersion() stringReturns the resource's API version (e.g. "apps/v1").
.Kind() stringReturns the resource kind (e.g. "Deployment").
.Data() map[string]anyReturns the full resolved resource data as a map.
.Get(path string) anyRetrieves a resolved value at the given path. Supports dot-notation ("spec.replicas"), array indices ("spec.containers[0].image"), and bracket notation ("metadata.labels[app.oam.dev/name]"). Returns nil if the path does not exist or the field was not set for the given test parameters.

Accessor Methods (Structure Inspection)

These methods on *ComponentDefinition (and its siblings TraitDefinition, PolicyDefinition, WorkflowStepDefinition) let you inspect the definition's declared structure without rendering. Use them in the BeforeEach or early It blocks of a Ginkgo suite to confirm the definition was wired correctly.

MethodDescription
comp.GetName() stringReturns the definition name passed to defkit.NewComponent(name).
comp.GetDescription() stringReturns the description set by .Description(...).
comp.GetWorkload() WorkloadTypeReturns a WorkloadType with .APIVersion() and .Kind() methods.
comp.GetParams() []ParamReturns all Param values registered via .Params(...). Each element implements Name() string, IsRequired() bool, IsOptional() bool, HasDefault() bool, GetDefault() any, and GetDescription() string.
comp.GetTemplate() func(tpl *Template)Returns the template closure. Invoke it with defkit.NewTemplate() to inspect the output and outputs builders without a test context: tpl := defkit.NewTemplate(); comp.GetTemplate()(tpl).
tpl.GetOutput() *ResourceReturns the primary output resource builder (nil if the template calls no tpl.Output(...)).
tpl.GetOutputs() map[string]*ResourceReturns all auxiliary resource builders keyed by their names. Resources registered with tpl.OutputsIf(...) are present in the map regardless of the condition.
comp.GetPlacement() placement.PlacementSpecReturns the PlacementSpec with .RunOn []placement.Condition and .NotRunOn []placement.Condition. Pass it to placement.Evaluate() in placement tests.
comp.ToCue() stringGenerates the full CUE definition string. Use in assertions that verify the generated CUE shape (parameter types, defaults, directives).

Gomega Matchers

Import the matchers package with a dot-import — import . "github.com/oam-dev/kubevela/pkg/definition/defkit/testing/matchers" — so matcher names are unqualified in test files. The full import path is github.com/oam-dev/kubevela/pkg/definition/defkit/testing/matchers.

Resource Kind and Version Matchers

Apply to any *defkit.Resource — either a raw builder (defkit.NewResource(...)) or the value returned by tpl.GetOutput() and tpl.GetOutputs()["name"].

MatcherDescription
BeDeployment()Passes when the resource's .Kind() is "Deployment".
BeService()Passes when the resource's .Kind() is "Service".
BeConfigMap()Passes when the resource's .Kind() is "ConfigMap".
BeSecret()Passes when the resource's .Kind() is "Secret".
BeIngress()Passes when the resource's .Kind() is "Ingress".
BeResourceOfKind(kind string)Passes when the resource's .Kind() equals the given string. Use for any kind not covered by the named matchers above.
HaveAPIVersion(version string)Passes when the resource's .APIVersion() equals the given string (e.g. "apps/v1", "v1").

Operation Matchers

Operation matchers inspect the builder's internal Set operation list. They run in-process and do not require rendering with a test context, making them the fastest way to verify that a field path is wired to the right value.

MatcherDescription
HaveSetOp(path string)Passes when the resource has a Set operation registered at exactly path. Use this to assert that a required field is wired.
HaveOpCount(n int)Passes when the total number of Set operations on the resource equals n. Use as a completeness check after verifying individual paths with HaveSetOp.
tip

HaveSetOp and HaveOpCount count the raw operations on the builder — not the resolved rendered fields. A SetIf creates one operation regardless of whether the condition is true at render time. Use rendered.Get(path) after comp.Render() to assert the resolved value; use HaveSetOp to assert the template wiring itself.

Parameter Matchers

Apply to any Param value (e.g. defkit.String("name"), defkit.Int("replicas").Default(1).Optional()).

MatcherDescription
BeRequired()Passes when param.IsRequired() returns true. A param is required only when .Required() has been called on it — a plain defkit.String("name") is neither required nor optional by default.
BeOptional()Passes when param.IsOptional() returns true. A param is optional only when .Optional() has been called on it.
HaveDefaultValue(v any)Passes when param.HasDefault() is true and param.GetDefault() equals v.
HaveDescription(desc string)Passes when param.GetDescription() equals desc exactly.
HaveParamNamed(name string)Applies to a *defkit.ComponentDefinition. Passes when any param in comp.GetParams() has Name() == name. Use as a quick existence check without iterating GetParams() by hand.
caution

BeRequired() and BeOptional() are independent — a param with neither .Required() nor .Optional() called will fail both matchers. This mirrors CUE semantics where a field with no marker is neither explicitly required nor optional.

Placement Testing Helpers

Test placement constraints by evaluating them against a mock cluster label map. Import "github.com/oam-dev/kubevela/pkg/definition/defkit/placement".

FunctionSignatureDescription
placement.Evaluatefunc Evaluate(spec PlacementSpec, labels map[string]string) PlacementResultChecks whether a cluster represented by labels satisfies the placement spec. Returns a PlacementResult with .Eligible bool, .Reason string, .MatchedRunOn []string, and .MatchedNotRunOn []string. Empty labels means no labels — Exists() / Eq() / In() conditions on RunOn will fail.
placement.ValidatePlacementfunc ValidatePlacement(spec PlacementSpec) errorDetects logically conflicting constraints at definition-build time — e.g. the same label required in both RunOn and NotRunOn. Returns *ValidationError or nil. Call this in a BeforeSuite or early It to catch mis-wired placement before rendering.

Obtain the PlacementSpec for any definition with comp.GetPlacement() (returns placement.PlacementSpec with .RunOn and .NotRunOn slices). The same method is available on TraitDefinition, PolicyDefinition, and WorkflowStepDefinition.

Example

Let's build a testing-demo component — a Deployment with an optional Service auxiliary — and exercise every API listed above in a single _test.go file: defkit.TestContext() with .WithName, .WithNamespace, .WithAppRevision, .WithClusterVersion, .WithParam, .WithParams; comp.Render() with .APIVersion(), .Kind(), .Get(path); comp.RenderAll() with .Primary and .Auxiliary; comp.GetName(), .GetDescription(), .GetWorkload(), .GetParams(), .GetTemplate(), .GetPlacement(); tpl.GetOutput(), .GetOutputs(); matchers BeDeployment(), BeService(), BeResourceOfKind(), HaveAPIVersion(), HaveSetOp(), HaveOpCount(), BeRequired(), BeOptional(), HaveDefaultValue(), HaveDescription(), HaveParamNamed(); and placement.Evaluate() plus placement.ValidatePlacement(). Building on the my-platform module scaffolded in Quick Start, drop both files below into my-platform/components/.

package components

import (
"github.com/oam-dev/kubevela/pkg/definition/defkit"
"github.com/oam-dev/kubevela/pkg/definition/defkit/placement"
)

func TestingDemo() *defkit.ComponentDefinition {
image := defkit.String("image").
Description("Container image")
replicas := defkit.Int("replicas").
Default(1).
Description("Number of replicas")
port := defkit.Int("port").
Optional().
Description("Container port to expose")

return defkit.NewComponent("testing-demo").
Description("Demonstrates defkit's testing surface: TestContext, Render, matchers, and placement.Evaluate").
Workload("apps/v1", "Deployment").
RunOn(
placement.Label("environment").Ne("development"),
).
Params(image, replicas, port).
Template(testingDemoTemplate)
}

func testingDemoTemplate(tpl *defkit.Template) {
vela := defkit.VelaCtx()
image := defkit.String("image")
replicas := defkit.Int("replicas")
port := defkit.Int("port")

dep := defkit.NewResource("apps/v1", "Deployment").
Set("metadata.name", vela.Name()).
Set("metadata.namespace", vela.Namespace()).
Set("spec.replicas", replicas).
Set("spec.selector.matchLabels[app.oam.dev/component]", vela.Name()).
Set("spec.template.metadata.labels[app.oam.dev/component]", vela.Name()).
Set("spec.template.spec.containers[0].name", vela.Name()).
Set("spec.template.spec.containers[0].image", image)

tpl.Output(dep)

svc := defkit.NewResource("v1", "Service").
Set("metadata.name", vela.Name()).
Set("spec.selector[app.oam.dev/component]", vela.Name()).
SetIf(port.IsSet(), "spec.ports[0].port", port)

tpl.OutputsIf(port.IsSet(), "service", svc)
}

func init() { defkit.Register(TestingDemo()) }

Reproduce the CUE on the right with:

vela def validate-module ./my-platform
vela def gen-module ./my-platform -o ./generated-cue

Apply and verify

The testing-demo component exercises a purely unit-level testing surface — comp.Render(), comp.RenderAll(), accessor methods, Gomega matchers, and placement.Evaluate() all run in-process. No cluster is required to run the test suite. Use -v to see the Ginkgo dots; without it, go test prints only the package-level pass line.

go test -v ./components/...
Running Suite: Components Suite - my-platform/components
=========================================================
Random Seed: 1779035494

Will run 237 of 237 specs
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••

Ran 237 of 237 Specs in 0.040 seconds
SUCCESS! -- 237 Passed | 0 Failed | 0 Pending | 0 Skipped
--- PASS: TestComponents (0.05s)
PASS
ok my-platform/components 0.056s

All 237 specs pass, including the TestingDemo Component suite that exercises every method in the tables above.

The same definition deploys cleanly against a live cluster — useful as a smoke test that your unit-tested template renders to a valid Kubernetes object the controller accepts:

# placement RunOn requires environment != development
kubectl create configmap vela-cluster-identity -n vela-system \
--from-literal=environment=production
vela def apply ./vela-templates/definitions/component/testing-demo.cue
vela up -f testing-demo-app.yaml # type: testing-demo, properties: image+replicas+port
NAME               COMPONENT      TYPE           PHASE     HEALTHY   STATUS   AGE
testing-demo-app testing-demo testing-demo running true 15s

The rendered Deployment and Service are named after the component (context.name), not the Application — metadata.name in the template is set to vela.Name(), which resolves to the component name (testing-demo).

kubectl get deployment testing-demo -o jsonpath='{.spec.replicas} {.spec.template.spec.containers[0].image}'
# 1 nginx:stable
kubectl get service testing-demo -o jsonpath='{.spec.ports}'
# [{"port":8080,"protocol":"TCP","targetPort":8080}]

Clean up:

vela delete testing-demo-app --namespace default -y
kubectl delete componentdefinition testing-demo -n vela-system
kubectl delete configmap vela-cluster-identity -n vela-system

See Also