I understand that you want to start a real http server for your integration test. You could extract the logic inside main method to another one and return the ActorSystem created.
public static ActorSystem<NotUsed> startActorSystem() {
//#server-bootstrapping
Behavior<NotUsed> rootBehavior = Behaviors.setup(context -> {
ActorRef<UserRegistry.Command> userRegistryActor =
context.spawn(UserRegistry.create(), "UserRegistry");
UserRoutes userRoutes = new UserRoutes(context.getSystem(), userRegistryActor);
startHttpServer(userRoutes.userRoutes(), context.getSystem());
return Behaviors.empty();
});
// boot up server using the route as defined below
return ActorSystem.create(rootBehavior, "HelloAkkaHttpServer");
//#server-bootstrapping
}
Then, you can start the real server invoking that method
var actorSystem = actorSystem = QuickstartApp.startActorSystem();
Now you can send real http requests to the server instead of testing the routes isolated.
Your new test should look like the following
import akka.NotUsed;
import akka.actor.typed.ActorSystem;
import akka.http.javadsl.Http;
import akka.http.javadsl.marshallers.jackson.Jackson;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.MediaTypes;
import org.junit.*;
import org.junit.runners.MethodSorters;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserRoutesIntegrationTest {
private static ActorSystem<NotUsed> actorSystem;
@BeforeClass
public static void beforeClass() {
// ---------------------------
// START THE REAL SERVER HERE
// ---------------------------
actorSystem = QuickstartApp.startActorSystem();
}
@AfterClass
public static void afterClass() {
// ------------------------------
// SHUT DOWN THE REAL SERVER HERE
// ------------------------------
actorSystem.terminate();
}
@Test
public void test1NoUsers() throws ExecutionException, InterruptedException {
// ------------------------------------------
// SEND REAL HTTP REQUEST TO REAL HTTP SERVER
// ------------------------------------------
var responseCompletionStage = Http
.get(actorSystem)
.singleRequest(HttpRequest.GET("http://localhost:8080/users"));
// ---------------------------
// UNMARSHAL THE HTTP RESPONSE
// ---------------------------
var usersCompletionStage = responseCompletionStage
.thenCompose(response -> Jackson.unmarshaller(UserRegistry.Users.class).unmarshal(response.entity(), actorSystem));
var users = usersCompletionStage.toCompletableFuture().get();
// ----------
// ASSERTIONS
// ----------
assertTrue(users.users().isEmpty());
}
@Test
public void test2HandlePOST() throws ExecutionException, InterruptedException {
var responseCompletionStage = Http
.get(actorSystem)
.singleRequest(
HttpRequest
.POST("http://localhost:8080/users")
.withEntity(
MediaTypes.APPLICATION_JSON.toContentType(),
"""
{
"name": "Kapi",
"age": 42,
"countryOfResidence": "jp"
}
"""
)
);
var actionPerformedCompletionStage = responseCompletionStage
.thenCompose(response -> Jackson
.unmarshaller(UserRegistry.ActionPerformed.class)
.unmarshal(response.entity(), actorSystem));
var actionPerformed = actionPerformedCompletionStage.toCompletableFuture().get();
assertEquals("User Kapi created.", actionPerformed.description());
}
@Test
public void test3Remove() throws ExecutionException, InterruptedException {
var responseCompletionStage = Http
.get(actorSystem)
.singleRequest(HttpRequest.DELETE("http://localhost:8080/users/Kapi"));
var actionPerformedCompletionStage = responseCompletionStage
.thenCompose(response -> Jackson
.unmarshaller(UserRegistry.ActionPerformed.class)
.unmarshal(response.entity(), actorSystem));
var actionPerformed = actionPerformedCompletionStage.toCompletableFuture().get();
assertEquals("User Kapi deleted.", actionPerformed.description());
}
}
This is a dummy example of an integration test because you only need to start the http server. In case you need a database, a message queue or even another real server running in a docker container you could use something like Java Testcontainers.
Have also in mind that you are hardcoding many things in your main class, such as the port or interface. It could be a good idea to do some refactor and receive some values or a Config object by param to make your tests more flexibles