import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.notNullValue;
@QuarkusTest
class MyTusTest {
@Test
void testCreateUpload() {
given()
.header("Tus-Resumable", "1.0.0")
.header("Upload-Length", "1024")
.when().post("/tus")
.then()
.statusCode(201)
.header("Location", notNullValue());
}
}
This guide covers testing strategies for applications that use the Quarkus TUS extension.
@QuarkusTest (Dev Mode)
@QuarkusTest starts your application in dev mode and allows full CDI injection. This is the primary way to test TUS upload behavior including CDI event observation.
Basic Test Setup
Observing CDI Events in Tests
Create a test observer bean to capture CDI events fired during uploads:
import io.quarkus.arc.Unremovable;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
import org.sitenetsoft.quarkus.tus.runtime.event.*;
import java.util.concurrent.CopyOnWriteArrayList;
@Singleton
@Unremovable
public class TusTestObserver {
public final CopyOnWriteArrayList<TusUploadCreatedEvent> createdEvents = new CopyOnWriteArrayList<>();
public final CopyOnWriteArrayList<TusUploadCompletedEvent> completedEvents = new CopyOnWriteArrayList<>();
public void onCreated(@Observes TusUploadCreatedEvent event) {
createdEvents.add(event);
}
public void onCompleted(@Observes TusUploadCompletedEvent event) {
completedEvents.add(event);
}
public void reset() {
createdEvents.clear();
completedEvents.clear();
}
}
Then inject and assert on it in your test:
@QuarkusTest
class MyTusEventTest {
@Inject
TusTestObserver observer;
@BeforeEach
void setUp() {
observer.reset();
}
@Test
void testCompletedEventFired() {
byte[] data = "test".getBytes();
String location = given()
.header("Tus-Resumable", "1.0.0")
.header("Upload-Length", String.valueOf(data.length))
.when().post("/tus")
.then().statusCode(201)
.extract().header("Location");
given()
.header("Tus-Resumable", "1.0.0")
.header("Upload-Offset", "0")
.contentType("application/offset+octet-stream")
.body(data)
.when().patch(location)
.then().statusCode(204);
assertFalse(observer.completedEvents.isEmpty());
}
}
|
Tip
|
Use @Singleton (not @ApplicationScoped) for test observers to avoid CDI client proxy issues when reading fields directly.
|
@TestProfile for Configuration Overrides
Use @TestProfile to override build-time or runtime configuration for specific test classes:
import io.quarkus.test.junit.QuarkusTestProfile;
import java.util.Map;
public class AuthEnabledProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Map.of("quarkus.tus.auth-enabled", "true");
}
}
@QuarkusTest
@TestProfile(AuthEnabledProfile.class)
class TusAuthTest {
// Tests run with authentication enabled
}
Custom UploadStore in Tests
To test a custom UploadStore implementation, use @Alternative @Priority(1) with getEnabledAlternatives() in a test profile:
@ApplicationScoped
@Alternative
public class InMemoryUploadStore implements UploadStore {
// Full implementation backed by ConcurrentHashMap
}
public class CustomStoreProfile implements QuarkusTestProfile {
@Override
public Set<Class<?>> getEnabledAlternatives() {
return Set.of(InMemoryUploadStore.class);
}
}
@QuarkusTest
@TestProfile(CustomStoreProfile.class)
class TusCustomStoreTest {
@Inject
UploadStore uploadStore;
@Test
void testInjectedStoreIsInMemory() {
assertInstanceOf(InMemoryUploadStore.class, uploadStore);
}
}
|
Tip
|
A custom store needs no protocol knowledge — the extension fires TusUploadCompletedEvent and the other lifecycle events itself. To check that your store honours the SPI contract (staged writes, offsets, locking, concatenation), extend AbstractUploadStoreContractTest from the quarkus-tus-tck artifact; see Custom Storage Backends.
|
@QuarkusIntegrationTest (Packaged JAR)
@QuarkusIntegrationTest tests your extension against the packaged application JAR — the same artifact you’d deploy to production. Unlike @QuarkusTest, CDI injection is not available; only HTTP-based tests work.
Setting Up IT Tests
Create test base classes with HTTP-only test methods, then extend them for both modes:
// Base class — no @QuarkusTest, no @Inject
abstract class TusUploadTestBase {
@Test
void testOptions() {
given().when().options("/tus")
.then().statusCode(204)
.header("Tus-Resumable", notNullValue());
}
// ... more HTTP-only tests
}
// Dev mode
@QuarkusTest
class TusUploadTest extends TusUploadTestBase {
// Add injection-dependent tests here
}
// Packaged JAR
@QuarkusIntegrationTest
class TusUploadIT extends TusUploadTestBase {
// Inherits all HTTP-only tests
}
Running IT Tests
The project provides dedicated Gradle tasks:
# Run @QuarkusIntegrationTest tests against the packaged JAR
JAVA_HOME=/usr/lib/jvm/java-25-openjdk-amd64 ./gradlew :integration-tests:integrationTest
|
Note
|
@QuarkusIntegrationTest classes cannot use @TestProfile — they run against the packaged app with its built-in configuration.
|
Native Image Testing
The same @QuarkusIntegrationTest classes can test a GraalVM native binary. No code changes are needed — just set the quarkus.native.enabled system property:
# Requires GraalVM or Mandrel installed
JAVA_HOME=/usr/lib/jvm/java-25-openjdk-amd64 ./gradlew :integration-tests:nativeIntegrationTest
This builds a native binary and runs all *IT test classes against it.
Manual test console
integration-tests serves a browser console for exercising the protocol by hand. It is part of the
test module only — it is never packaged into quarkus-tus or quarkus-tus-deployment.
./gradlew :integration-tests:quarkusDev
# then open http://localhost:8080/
Each extension has its own section: core upload with pause and resume, creation-with-upload, creation-defer-length, checksum (valid and deliberately corrupt), concatenation via parallel partial uploads, expiration, termination, and live SSE progress.
The point of it is the protocol log on the right. Every request the page makes is logged with its method, URL, status and the TUS headers that matter, so you can watch an upload resume from its real offset, see a corrupt checksum rejected with 460 while the offset stays put, and see partials merge into a final upload. That is the part automated tests assert but never show you.
The page speaks TUS directly with fetch rather than using a client library, so nothing about the
wire format is hidden behind an abstraction.
|
Note
|
application.properties sets a 1 KB max-chunk-size so the tests exercise the limit. Dev mode
overrides it via %dev. keys, otherwise the console would hit 413 on any real file.
|