I have an akka based application, and it has Application.java file which has structure like what we have defined here.
Now I want to write integration tests for akka http endpoints in this application, as discussed here.
It says that I need to launch application in @BeforeClass method.
So how do I start application. I guess merely calling main function wont work as it would block the program and tests would never hit.
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