Writing Radius functional tests
July 13, 2026 ยท View on GitHub
Purpose
This guide explains how to add a functional test to the portable Radius test suite. Functional tests deploy real applications and resources to Kubernetes or a cloud provider and validate complete user scenarios. Use them when a change cannot be covered by the self-contained unit and integration tests that run through make test.
For instructions on running the suite, including its prerequisites and cleanup behavior, see Running Radius functional tests.
Prerequisites
- Complete the setup in Running Radius functional tests.
- Choose the existing test group that owns the behavior you are testing. The Make targets and package paths for every group are defined in
build/test.mk. - Read a nearby test in the same group and follow its setup and validation patterns.
Steps
1. Choose the package
Portable functional tests live under:
test/functional-portable/<group>[/<cloud-or-noncloud>][/<kind>]
For example, non-cloud Core RP resource tests live under test/functional-portable/corerp/noncloud/resources, while upgrade tests live directly under test/functional-portable/upgrade. The group names generally match the make test-functional-<group> targets; the Messaging RP target is named msgrp, while its source directory is messagingrp. Use the exact package path in build/test.mk for the group you are changing.
Put .bicep files and other fixtures in a testdata directory inside the test package.
2. Follow the current test harness
Most resource-provider tests use rp.NewRPTest, one or more rp.TestStep values, a deploy executor, and explicit resource or Kubernetes-object validation. A minimal test has this shape:
package resource_test
import (
"testing"
"github.com/radius-project/radius/test/rp"
"github.com/radius-project/radius/test/step"
"github.com/radius-project/radius/test/validation"
)
func Test_DescriptiveTestName(t *testing.T) {
name := "unique-test-name"
template := "testdata/unique-test-name.bicep"
test := rp.NewRPTest(t, name, []rp.TestStep{
{
Executor: step.NewDeployExecutor(template, ""),
RPResources: &validation.RPResourceSet{
Resources: []validation.RPResource{
{
Name: name,
Type: validation.ApplicationsResource,
},
},
},
},
})
test.Test(t)
}
Copy a nearby test rather than this skeleton when the scenario needs recipe modules, cloud credentials, custom cleanup, output-resource validation, or Kubernetes assertions.
3. Keep the test isolated
- Give applications, environments, and resources names that are unique across the repository.
- Follow the functional-test naming conventions.
- Keep non-cloud tests independent of cloud accounts and cloud resources.
- Add readiness probes to test containers when the test needs to assert that a workload becomes ready.
- Prefer the existing validation sets over custom
PostStepVerifyorPostDeleteVerifycallbacks. Add a callback only when the shared validation framework cannot express the assertion. - Put cleanup in the test harness so failed tests do not leave resources behind.
4. Run the narrowest target
Run the package directly while iterating:
go test ./test/functional-portable/<package-path>/...
Then run its Make target before opening a pull request:
make test-functional-<group>-<cloud-or-noncloud>
Verification
- The new test passes through both
go teston its package and the matchingmake test-functional-*target. - Test fixtures are under the package's
testdatadirectory. - Resource names follow the naming conventions and do not collide with another test.
- The test cleans up everything it creates.
Troubleshooting
- The deployment succeeds but validation fails. Compare the
validation.RPResourceSetorvalidation.K8sObjectSetwith a nearby test for the same resource type. - A Terraform recipe test cannot find its module. Publish the test recipes and set the module server URL as described in Running Radius functional tests.
- The test passes alone but fails in a group. Check for reused application, environment, namespace, or resource names.