For JUnit test authoring, see the JUnit testing guide.
Prefer @TableTest for parameterized tests with multi-column literal data. When a simple @TypeConverter can turn
table values into the required arguments, prefer it over switching to @MethodSource. Use @MethodSource for cases
requiring complex object construction, builders, or mocks.
The project leverages different types of tests:
- The most common ones are unit tests.
They are intended to test a single isolated feature, and rely on JUnit 5 framework or Spock 2 framework.
- JUnit framework is recommended for most unit tests for its simplicity and performance reasons.
- Spock framework provides an alternative for more complex test scenarios, or tests that require Groovy Script to access data outside their scope limitation (eg private fields).
-
A variant of unit tests is instrumented tests.
Their purpose is similar to unit tests, but the tested code is instrumented by the java agent (:dd-trace-java:java-agent) while running. They extend the Spock specificationdatadog.trace.agent.test.InstrumentationSpecificationwhich produces traces and metrics for testing. Instrumentation test harnesses always use strict trace writes so unfinished asynchronous work fails visibly instead of producing an incomplete trace. -
The third type of tests is Muzzle checks.
Their goal is to check the Muzzle directives, making sure instrumentations are safe to load against specific library versions.coreJdk(version)and library checks withjavaVersionfingerprint the selected JDK's major version, vendor, full runtime/VM versions, OS, and architecture;coreJdk()tracks the Gradle daemon JVM instead. Changing these values invalidates cached results even within the same Java major version. -
The fourth type of tests is integration tests.
They test features that require a more complex environment setup. In order to build such environment, integration tests use Testcontainers to set up the services needed to run the tests. -
The fifth type of test is smoke tests.
They are dedicated to test the java agent (:dd-java-agent) behavior against demo applications to prevent any regression. All smoke tests are located into the:dd-smoke-testsmodule. -
The last type of test is system tests.
They are intended to test behavior consistency between all the client libraries, and rely on their own GitHub repository.
Tip
Most of the instrumented tests and integration tests are instrumentation tests.
Independent of the type of test, test can be run in another (forked) JVM than the one running Gradle.
This behavior is implicit when the test class name is suffixed by ForkedTest (eg SomeFeatureForkedTest).
This mechanism exists to make sure either java agent state or static data are reset between test runs.
Note
Forked tests are not run as part of the gradle test task.
In order to run them, you need to use the forkedTest task instead.
Mark unreliable test methods or classes with @Flaky in both JUnit and Spock.
All tests run by default. Use -PskipFlakyTests to skip flaky tests or -PrunFlakyTests to run only flaky tests.
If a JUnit test is flaky only in certain environments, use conditionMethod to reference a static,
no-argument method that returns true when the test is flaky:
@Test
@Flaky(conditionMethod = "datadog.environment.JavaVirtualMachine#isIbm")
void testOnSupportedJvms() {
// ...
}Use suites = {"SomeSubclass"} to limit the annotation to the concrete class executing the test,
such as a subclass that inherits the annotated method. Simple and canonical class names are
supported. Matching is exact, so subclasses and nested classes must each be listed. When both
suites and a condition are specified, both must match.
Tip
In case your pull request checks failed due to some unexpected flaky tests, you can retry the continuous integration pipeline on Gitlab
Important
Don't use image name in Test Container constructors like new CassandraContainer("cassandra:4"),
or new GenericContainer("icr.io/appcafe/websphere-traditional:latest"). Image tags can change.
Also, these are not properly tracked as test task inputs and as such can't be fingerprinted.
Instead, use the dd-trace-java.testcontainers plugin to declare these as dependencies,
it will resolve the actual image digest before running the test.
Declare container images in the module's Gradle build so a changed image cannot silently reuse cached test results:
import datadog.buildlogic.testcontainers.image
plugins {
id("dd-trace-java.testcontainers")
}
dependencies {
testImplementation(libs.testcontainers)
testImplementation("com.redis.testcontainers:testcontainers-redis:1.6.2")
testContainerImage(image("redis:7-alpine", "test.redis.image"))
}Use GenericContainer or the dedicated container type and use its DockerImageName constructor
overload with the relevant system property, without a fallback value. The compatibility
declaration is important to let testcontainer know it should accept it as a mirrored image.
For example with Redis:
import com.redis.testcontainers.RedisContainer;
import org.testcontainers.utility.DockerImageName;
DockerImageName image = DockerImageName.parse(System.getProperty("test.redis.image"))
.asCompatibleSubstituteFor("redis");
RedisContainer redis = new RedisContainer(image);Essentially the plugin resolves tags to immutable registry digests before Gradle checks whether test results are up to date or cached. Resolution failure stops the test task earlier, rather than within the tests.
The plugin feed the system property to both test and forkedTest tasks.
testContainerImage is a "companion" for the testImplementation configuration.
The plugin automatically creates an image configuration for each *Implementation
configuration. For example, integrationTestImplementation gets
integrationTestContainerImage. Also, see the plugin reference
for inheritance and shared configuration examples.
Instrumentation modules whose tests use Netty 4 or shaded Netty should apply the Netty test convention:
plugins {
id 'dd-trace-java.module.instrumentation'
id 'dd-trace-java.instrumentation.netty-concurrent-tests'
}This opt-in convention lives in build-logic/conventions/src/main/kotlin/ and configures the module
when dd-trace-java.module.instrumentation is applied. It adds the netty-concurrent-4.0
instrumentation to the test classpath so that Netty tasks captured
by java-concurrent can activate and release their context. Additional test suites inherit this
dependency. It does not add a Netty library dependency or change production dependencies.
Instrumentation test harnesses always enable strict trace writes; there is no harness opt-out. Do not replace the harness tracer or introduce another way to disable strict writes.
Fix continuation leaks when possible.
If a fix cannot be included immediately, quarantine the Spock test with @Flaky and a useful reason or tracked issue so
the failure remains visible.
@Flaky is not yet supported for JUnit tests.
Keep continuation tracking enabled so the diagnostic evidence is preserved.
@TrackScopeContinuations records every detected lifecycle condition by default. Use disabledChecks
to exclude checks from enforcement:
// Exclude an enforced check only for a proven diagnostic incompatibility.
@TrackScopeContinuations(
disabledChecks = ScopeDiagnosticsCheck.ACTIVATE_AFTER_RESOLVE,
reason = "the synthetic fixture models failed activation without a real continuation; remove with ABC-123")An empty disabledChecks list keeps all checks active, including checks added in the future. A
nonempty list excludes only the listed checks and includes future checks automatically. A method
annotation replaces the class configuration; exclusion lists are not merged.
enabled = false stops recording entirely and requires an empty disabledChecks list. A reason is
required whenever enabled = false or disabledChecks is nonempty. Document the incompatibility
and its removal condition.
With recording enabled, all conditions remain recorded and reported even when every check is excluded. Excluding checks changes enforcement only; it does not disable recording, classification, or the bounded wait for asynchronous cleanup.
The diagnostic policy determines which findings fail a test and which are advisory. Excluding a check affects enforcement without removing its diagnostic evidence. Reports distinguish findings that caused a failure from those retained for context.
Disable recording or individual checks only for a proven incompatibility with the diagnostic itself, never to hide an unresolved lifecycle bug. Strict trace writes remain enabled.
You can run the whole project test suite using ./gradlew test but expect it to take a certain time.
Instead, you can run tests for a specific module (ex. :dd-java-agent:instrumentation:opentelemetry:opentelemetry-1.4)
using the test command for this module only:
./gradlew :dd-java-agent:instrumentation:opentelemetry:opentelemetry-1.4:test.
Tip
Flaky tests can be disabled by setting the Gradle property skipFlakyTests (ex. ./gradlew -PskipFlakyTests <task>).
To run tests on a different JVM than the one used for the build, you can specify it using the -PtestJvm= command line
option to the Gradle task:
-PtestJvm=Xlike-PtestJvm=8,-PtestJvm=25to run with a specific JDK version,-PtestJvm=/path/to/jdkto run with a given JDK,
To also run CI tests on the additional vendor and pre-release JVMs, include the exact, case-sensitive
[ci: NON_DEFAULT_JVMS] text in the commit message. By default, CI runs amd64 tests only.
On pull requests, arm64 tests are manual jobs. To run them on CI, include one of the following exact, case-sensitive texts in the commit message:
[ci: ARM64]to run them on the default arm64 JVMs.[ci: ARM64, NON_DEFAULT_JVMS]to run them on all JVMs.
The system tests are setup to run on continuous integration (CI) as pull request check
using a dedicated workflow.
To run them locally, grab a local copy of the system tests and run them from
there.
You can make them use your development version of dd-trace-java by dropping the built artifacts to the
/binaries folder of your
local copy of the system tests.
The APM test agent emulates the APM endpoints of the Datadog Agent.
The APM Test Agent container runs alongside Java tracer Instrumentation Tests in CI,
handling all traces during test runs and performing a number of Trace Checks.
Trace Check results are returned within the Get APM Test Agent Trace Check Results step for all instrumentation test
jobs.
Check trace invariant checks for more
information.
The APM Test Agent also emits helpful logging, including logging received traces' headers, spans, errors encountered, and information on trace checks being performed.
Logs can be viewed in GitLab within the Test-Agent container step for all instrumentation test suites, e.g. the
test_inst jobs.
Read more about the APM Test Agent.
Test output remains available in GitLab job artifacts. To also forward JUnit-captured output to Datadog, include the
exact, case-sensitive [ci: DEBUG_LOGS] token in the commit message or set the GitLab CI variable
DD_CIVISIBILITY_LOGS_ENABLED=true.
The opt-in forwards all JUnit-captured output, not only DEBUG messages.

