Using Podman to run Testcontainers in a Quarkus project
Tags: quarkus, podman, testing, testcontainers
Published: 2022-11-29 20:12:20

In the area of container engines, podman seems to be a relevant alternative to docker, at least during the development phase on a Windows or macOS machine. Indeed, both podman and podman-desktop, it's desktop UI, do not require a paid licence. Which is not the case anymore for Docker-Desktop under certain conditions (it's still free for individual developers, education, open source communities, and small businesses). In addition to that, podman is daemonless and can run in rootless mode.

This article shows how to use podman as a container engine instead of Docker and Docker-Desktop on Windows and macOS. We'll see how to install this tool and how to configure it to run tests that rely on Testcontainers. So let's get started with the prerequisites.

## Prerequisites

Even though you can use the official installers, I suggest to use package managers moving forward.

- On Windows, you can use [scoop](https://scoop.sh)
- On macOS, you can use [sdkman](https://sdkman.io/) for java-related tools ad [homebrew](https://brew.sh/) for the others.

- Java JDK 17
- podman
- Optionnal: podman-desktop
- On Windows, [WSL needs to be enabled](https://learn.microsoft.com/windows/wsl/install)

## Start and configure podman

Open a terminal and run these commands:

- `podman machine init`, which initializes a new VM. You just need to run it once.
- `podman machine start`, which starts the VM. You need to re-run it after each reboot.
- Podman-desktop allows to configure the podman machine to auto-start. You can start if from the apps menu.

![Podman desktop app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yn4idyr9f73mliq6hcwl.png)

![Podman desktop auto-start settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2nn79nrubiww9hbow5s.png)

- Now we are ready to launch containers. Run `podman run hello` which launches a basic container.
- The last thing we need to configure is to set this environment variable to `TESTCONTAINERS_RYUK_DISABLED` to `true`

The next section demonstrates that we can use Testcontainers through a sample project.

## Sample Quarkus project that uses Testcontainers

Let's create with an app with Quarkus which consists of a REST API that backs its data on a postgreSQL database.

Quarkus provides [zero-confguration databases](https://quarkus.io/guides/databases-dev-services) for development and test modes (called __DEV SERVICES FOR DATABASES__). It relies on a containerization tool, such as docker or podman, for some databases. Since we have podman installed and configured, we are ready to go.

In order to create a starter project, open [code.quarkus.io](https://code.quarkus.io/) and choose __JDBC Driver - PostgreSQL__ extension and any other extensions that you'd like to use for you project. 

In the following, I have selected the following extensions just for fun 😁.

- YAML Configuration
- JDBC Driver - PostgreSQL
- Hibernate ORM with Panache
- Hibernate ORM with Panache and Kotlin
- Kotlin
- RESTEasy Reactive
- RESTEasy Reactive Kotlin Serialization

I also selected __Gradle with Kotlin SDL__ build tool, but you can use any available one.

Once the applications information and extensions are selected, generate and download the zip archive. After that, unzip that archive and open a terminal on the root folder of the project (the one contains __pom.xml__, __build.gradle__ or __build.gradle.kts__).

Next, run the tests using `.\gradlew.bat test` (if you are using powershell). You should see the following output:

```
********************************************************************************
Ryuk has been disabled. This can cause unexpected behavior in your environment.
********************************************************************************
2022-11-27 00:16:55,210 INFO  [org.tes.DockerClientFactory] (build-57) Checking the system...
2022-11-27 00:16:55,212 INFO  [org.tes.DockerClientFactory] (build-57) ?? Docker server version should be at least 1.6.0
2022-11-27 00:16:55,241 INFO  [org.tes.uti.ImageNameSubstitutor] (build-57) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2022-11-27 00:16:55,284 INFO  [doc.io/postgres:14]] (build-57) Pulling docker image: docker.io/postgres:14. Please be patient; this may take some time but only needs to be done once.
...
BUILD SUCCESSFUL in 21s
9 actionable tasks: 1 executed, 8 up-to-date
```

The tests were executed successfully. The output proves that a container image has been downloaded and started. We can deduce that Podman was successfully able to interact with Testcontaiers, which is the library that is responsible for testing with containers.

Let's analyse this output. The following line means that the environment variable that we set earlier was detected.

> Ryuk has been disabled. This can cause unexpected behavior in your environment.

This line means that postgreSQL container image __docker.io/postgres:14__ is being downloaded.

> 2022-11-27 00:16:55,284 INFO  [doc.io/postgres:14]] (build-57) Pulling docker image: docker.io/postgres:14. Please be patient; this may take some time but only needs to be done once.

Finally, we can check with podman the presence of a postgreSQL image. Open a terminal and run `podman images`, you should see an output similar to this one.

```
REPOSITORY                  TAG         IMAGE ID      CREATED      SIZE
docker.io/library/postgres  14          c920f82d2df2  11 days ago  384 MB
```

Indeed, the postgres image is present. We have used containers with Podman. Horaaay !

### Testing with an entity

Even though we validated that podman works with testcontainers, we did not really use the database yet. Thus, for the sake of completion, let's create a Panache entity as well as a repository, and update our REST resource and tests to use this entity.

Here is the code on the app side:

```kotlin
@Entity
@Serializable
class Greeting : PanacheEntity() {
    lateinit var message: String
}

@ApplicationScoped
class GreetingRepository : PanacheRepository<Greeting> {
    fun findByPrefix(prefix: String) = list("message like ?1", "$prefix%")
}

@Path("/")
class GreetingResource {

    @Inject
    lateinit var greetingRepository: GreetingRepository

    @GET
    @Path("/greetings/{prefix}")
    @Produces(MediaType.APPLICATION_JSON)
    fun greetings(@PathParam("prefix") prefix: String) = greetingRepository.findByPrefix(prefix).orEmpty()
}
```

The library used for entity mangement is called [Panache](https://quarkus.io/guides/hibernate-orm-panache) and allows to use the repository pattern with Hibernate.

⚠ We added `@Serializable` on the entity so that it can be parsed into and from JSON. However, the Quarkus project generator did not add an important plugin that makes serialization work with Kotlin. Please add this plugin to __build.gradle.kts__ in order to enable this feature: `id("org.jetbrains.kotlin.plugin.serialization") version "1.7.21"`

The plugin part should look like this:

```kotlin
plugins {
    kotlin("jvm") version "1.7.21"
    kotlin("plugin.allopen") version "1.7.21"
// add this line
    id("org.jetbrains.kotlin.plugin.serialization") version "1.7.21"
    id("io.quarkus")
}
```

This is the test code. The method `prepareData` adds initial data to the database and `testHelloEndpoint` tests the above endpoint. The library that is used for testing the REST API is called [rest-assured](https://rest-assured.io/) and provides a nice DSL (Domain Specific Language) with functions such as `given` and `when`.

```kotlin
@QuarkusTest
class GreetingResourceTest {

    @Inject
    lateinit var greetingRepository: GreetingRepository

    @Transactional
    @BeforeEach
    fun prepareData() {
        greetingRepository.deleteAll()
        val greeting = Greeting()
        greeting.message = "hello world"
        greetingRepository.persist(greeting)
        greetingRepository.flush()
    }

    @Test
    fun testHelloEndpoint() {
        given()
            .`when`().get("/greetings/hello")
            .then()
            .statusCode(HttpStatus.SC_OK)
            .body("$.size()", equalTo(1))
            .body("message", hasItem("hello world"))
    }

}
```

As stated earlier, no database configuration is needed, thanks to Quarkus' Dev Services. We just need to run `.\gradlew.bat test` to run the tests and `./gradlew quarkusDev` to run the development server.

## Conclusion

This article showed how to use podman as a container tool on a Quarkus + testcontainers project. The most important setting that make Podman and Testcontainers interact with each other is setting the environment variable `TESTCONTAINERS_RYUK_DISABLED` to `true`.

Happy coding