Getting Started With Javalin
In this post, we’ll create a small hello world application with Javalin based on Gradle Kotlin and then test of our application.
Join the DZone community and get the full member experience.
Join For FreeWith the popularity of Spring Boot and the great support for integrating with different types of frameworks, it’s often hard to imagine using something different than Spring. However, there are numerous frameworks out there which provide similar features for building applications like microservices, and Javalin is one of those frameworks. This post will help you to get started with Javalin.
What Is Javalin?
Javalin is a web framework for building Java and Kotlin web applications in a similar fashion to frameworks like Node.js (see Koajs.com) or SparkJava, which is not surprising, since the author is one of the original SparkJava authors.
Creating a Gradle Build for Javalin
In this post, we’ll create a small hello world application based on Gradle Kotlin, including the testing of our application.
To get started with Javalin, we need to have a Javalin dependency. An easy way to do that is to use Gradle. Assuming you have Gradle installed, we can create a new project with the following command:
gradle init --type java-library --dsl kotlin
(Answer the prompted questions, their values aren’t really important for the purpose of this guide.)
Once the project has been initialized, open the build.gradle.kts file, and replace the contents with the following content:
// build.gradle.kts
plugins {
val kotlinVersion = "1.3.21"
id("org.jetbrains.kotlin.jvm") version kotlinVersion
application
}
application {
// Define the main class for the application and add Kt for Kotlin classes
mainClassName = "HelloWorldKt"
}
dependencies {
compile("io.javalin:javalin:2.8.0")
compile("org.slf4j:slf4j-simple:1.7.26")
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
}
repositories {
jcenter()
}
The main library used here is the Javalin 2.8.0 library, which at the time of writing this piece is the latest one. After this, navigate to src/main/kotlin
, and create a HelloWorld.kt
file. This file will contain the contents of our complete application.
// src/main/kotlin/HelloWorld.kt
import io.javalin.Javalin
fun main() {
val app = Javalin.create().start(7000)
app.get("/") { ctx -> ctx.result("Hello World") }
}
Now, running gradlew run
from a terminal will start up the Javalin application, running on port 7000. Opening http://localhost:7000 in your browser will result in the root route being called (app.get("/")
), which will display the Hello World text in your browser. Now you’re ready to include more functionality into your app and to write a test for it, which I’ll dive into in the next section.
Testing Javalin
After creating our initial application, we will dive into a way to easily create integrations tests. We'll use the latest version of JUnit 5, plus we’ll use Fuel HTTP to test our endpoint.
What Is Fuel HTTP?
Fuel HTTP is one of the easiest networking libraries for Kotlin and quite actively developed. Fuel HTTP provides great support for handling standard HTTP calls out of the box. There are also modules which support other frameworks such as GSON, Jackson, RxJava, and many more. This makes Fuel HTTP well suited for testing REST endpoints, which is what we’ll test in this post.
Our Application Overview
We'll enhance our existing application we wrote before. We'll adapt it so it retrieves persons by invoking an HTTP GET call with an id parameter to a person endpoint, such as /api/person/1
, where 1 is the id of the person. The result of this will be a JSON structure representing the person, as can be seen below:
{"name":"Yvonne","age":29}
The total solution consists of the following:
- The Gradle build file to manage our dependencies.
- The application configuration including the routing.
- The person domain, including a controller, a DTO and a repository.
- The actual integration tests.
The Gradle Build File
// build.gradle.kt
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
val kotlinVersion = "1.3.21"
id("org.jetbrains.kotlin.jvm") version kotlinVersion
application
}
tasks.withType<Test> {
useJUnitPlatform()
}
dependencies {
api("io.javalin:javalin:2.8.0")
implementation("org.slf4j:slf4j-simple:1.7.26")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8")
testImplementation("org.junit.jupiter:junit-jupiter:5.4.1")
testImplementation("com.github.kittinunf.fuel:fuel:2.0.1")
testImplementation("com.github.kittinunf.fuel:fuel-jackson:2.0.1")
}
repositories {
jcenter()
}
application {
// Define the main class for the application
mainClassName = "ApplicationKt"
}
As you can see in the Kotlin Gradle build file above, we've changed the build file to include our production and test dependencies. Some of the dependencies we want to expose are defined as API dependencies. The implementation details are hidden from anyone having a dependency on our application. This is done by using the implementation dependencies. Furthermore, we also have our Fuel and JUnit 5 test include testImplementation
dependencies, which are used to execute our integration tests.
Application Configuration and Routing
// Application.kt
import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.apibuilder.ApiBuilder.path
import person.PersonController
import person.personRepository
fun main() {
JavalinApp(7000).init()
}
class JavalinApp(private val port: Int) {
fun init(): Javalin {
val app = Javalin.create().apply {
port(port)
exception(Exception::class.java) { e, _ -> e.printStackTrace() }
}.start()
val personController = PersonController(personRepository)
app.get("/api/person/:id", personController::getPerson)
return app
}
}
In the above, we've defined our Javalin app. It's extracted to a separate class to make it easier to invoke it from the test. The Javalin class is responsible for managing the running application, which includes setting up the controller and the API routes.
Person Domain Model
// person.Person.kt
package person
import io.javalin.Context
class PersonController(private val data: Map<Int, Person>) {
fun getPerson(ctx: Context) {
ctx.pathParam("id").toInt().let {
data[it]?.let { item ->
ctx.json(item)
return
}
ctx.status(404)
}
}
}
data class Person(val name: String, val age: Int)
// In-memory repository
val personRepository = hashMapOf(
0 to Person("Dmitry", 37),
1 to Person("Yvonne", 29),
2 to Person("Peter", 52)
)
The above code, inspired by one of the Javalin tutorials, has been merged together into one file. This isn't necessarily a great idea, but for the purpose of our post, this was easier. It contains a controller which is using our injected repository, a person class, and the in-memory repository itself containing our dummy persons.
Integration Tests
// PersonIntegrationTest.kt
package person
import JavalinApp
import com.github.kittinunf.fuel.core.FuelManager
import com.github.kittinunf.fuel.httpGet
import com.github.kittinunf.fuel.jackson.responseObject
import io.javalin.Javalin
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.assertEquals
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("Person API")
class PersonIntegrationTest {
private lateinit var app: Javalin
@BeforeAll
fun setUp() {
app = JavalinApp(8000).init()
// Inject the base path to no have repeat the whole URL
FuelManager.instance.basePath = "http://localhost:${app.port()}/"
}
@AfterAll
fun tearDown() {
// Stops the application when the tests have completed
app.stop()
}
@Test
fun `should get result for existing person`() {
// Deconstructs the ResponseResult
val (_, _, result) = "api/person/0".httpGet().responseObject<Person>()
// Get the actual value from the result object
assertEquals(personRepository[0], result.get())
}
@Test
fun `should get error for non-existing person`() {
val (_, _, result) = "api/person/-1".httpGet().responseObject<Person>()
// Deconstructs the ResponseResult to get the FuelError
val (_, error) = result
assertEquals(404, error!!.response.statusCode)
}
}
And now, after setting up our production infrastructure, this is our end result. It's an integration test which is testing two scenarios: a scenario in which there is a result found and a scenario which results into an error.
In the above code, we're using the synchronous version of Fuel HTTP, which is blocking until there is a result. Then we destructure the result into separate fields and extract the ones we need.
Executing the test using gradlew test
or using your favorite IDE will result in the following test report:
Person API
+-- should get error for non-existing person [PASSED]
+-- should get result for existing person [PASSED]
While the example above is quite limited and in no way covers all the aspects of REST endpoint, it hopefully sets you up to start testing Javalin applications!
Published at DZone with permission of Erik Pragt. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments