Realtime Testing: Micronaut, Postgres and Testcontainer
Micronaut, Java and Testcontainer
Till not we have been able to create the application in Micronaut using JDBC using the H2 database. We even were able to do a CRUD operation using reactive api. Any application is not going to production without test cases. If your test cases are poor, you are not sure about their performance and bugs. In this article we will explore writing test cases using Micronaut, Postgres and Testcontainers
You can find working source code at https://github.com/CODINGSAINT/super-heroes/tree/04AddingTestWithTestContainer
Why Testcontainers
Testcontainers are a great way for writing tests for your application. It largely removes the pain of mocking the objects to write your test cases. While it is OK to mock in unit tests, I started liking the Testcontainer's concept as it seems to write tests for more real cases. The responses from Third Pary API make a perfect case to mock. Here we are using POSTGRES database which our system will interact , so let us create Tests with Testcontainers.
If you have not read the previous CRUD solutions you can refer Saving Superheroes reactively: Reactive JDBC with Micronaut. It has code and explanations till now. In the previous section we used H2 as a database, now let us switch to Postgres. Anyway in the real world, there are negligible chances of using H2. We will also add test container related dependencies.
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- 04 Testcontainers Start -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<!-- 04 Testcontainers ends-->
Let us change theapplication.yml
to use Postgres
db rather H2
datasources:
default:
url: ${JDBC_URL:`jdbc:postgresql://localhost:5432/superhero`}
username: ${JDBC_USER:dbuser}
password: ${JDBC_PASSWORD:theSecretPassword}
driverClassName: ${JDBC_DRIVER:org.postgresql.Driver}
schema-generate: CREATE_DROP
dialect: POSTGRES
I have also updated the Repository dialect at ReactiveSuperheroRepository
which are creted to work with JDBC reactive endpoints
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface ReactiveSuperheroRepository extends ReactiveStreamsCrudRepository<Superhero, Long> {
@NonNull
@Override
Mono<Superhero> findById(@NonNull Long aLong);
@NonNull
@Override
Flux<Superhero> findAll();
}
If you start your system, it should work. Also, note Postgres instance must be running. I have used docker to run postgres
docker run -it --rm \
-p 5432:5432 \
-e POSTGRES_USER=dbuser \
-e POSTGRES_PASSWORD=theSecretPassword \
-e POSTGRES_DB=superhero \
postgres:11.5-alpine
Now, we will write the test cases. Before we write, we need to create an instance for Postgres to run with test containers. Below is the base class for the writing test with the Postgres container.
You can stop the docker instance from running Postgres. Testcontainer will create it while tests are run.
Start by creating a file in test resource folder called application-test.yml , which will be picked by the system as the profile test is added as default. It will also help Testcontainer to pick the Postgres container and start. Add the following content to it.
datasources:
default:
url: jdbc:tc:postgresql:12:///postgres
driverClassName: org.testcontainers.jdbc.ContainerDatabaseDriver
Create a class for Test or add the following content (annotation and body) in your test class
@Testcontainers
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@MicronautTest
class SuperHeroesTest {
@Inject
EmbeddedApplication<?> application;
Superhero superhero = null;
@Inject
@Client("/")
private StreamingHttpClient client;
}
@Testcontainers
: Annotation for Testcontainer.@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
: Run the method in the order provided, it is important as we would like to run the test in order ( create, retrieve, update, delete).@TestInstance(TestInstance.Lifecycle.PER_CLASS)
: Full test makes one complete cycle.@MicronautTest
: Let micronaut know that it's the test and injection for embedded server and dependency happen.EmbeddedApplication<?> application
: The full embedded server on which application will start and runStreamingHttpClient
: It wil help us to do Http requests on the embedded server.
Let us now add the test for all of the CRUD operations. We will use Faker to generate faker superheroes' names and power.
Testing HTTP POST method to create a superhero
Here we are creating a Superhero and adding its name with Faker (if you with you can hardcode ). We are calling HTTP POST endpoint on the embedded server. It will use the database created by Postgres to run the DB query and create an entry in it. We will assert that returned user has the same values are created.
@Test
@Order(1)
void insert() {
var faker = new Faker();
var superHero = faker.superhero();
String name = superHero.name();
String prefix = superHero.prefix();
String suffix = superHero.suffix();
String power = superHero.power();
superhero = new Superhero(null, name, prefix, suffix, power);
var request = HttpRequest.POST("/rx/superhero", superhero);
var response = client.toBlocking().exchange(request, Superhero.class);
Assert.assertEquals(HttpStatus.OK, response.getStatus());
Assert.assertEquals(name, response.body().name());
Assert.assertEquals(prefix, response.body().prefix());
}
Testing HTTP GET method to retrieve superhero
Let us retrieve the user created. Since we have created only one user, it will have id 1. We will assert tht user is same as created.
@Test
@Order(2)
void get() {
var request = HttpRequest.GET("/rx/superhero/1");
var response = client.toBlocking().exchange(request, Superhero.class);
Assert.assertEquals(HttpStatus.OK, response.getStatus());
Assert.assertEquals(superhero.name(), response.body().name());
}
Testing HTTP PUT method to update superhero
Now we will update the user with PUT and assert that it has been updated with correct values
@Test
@Order(3)
void update() {
String name = superhero.name() + "_Modified";
String prefix = superhero.prefix() + "_Modified";
Superhero hero = new Superhero(1L, name, prefix, superhero.suffix(), superhero.power());
var request = HttpRequest.PUT("/rx/superhero", hero);
var response = client.toBlocking().exchange(request, Superhero.class);
Assert.assertEquals(HttpStatus.OK, response.getStatus());
Assert.assertEquals(name, response.body().name());
Assert.assertEquals(prefix, response.body().prefix());
}
Testing HTTP DELETE method to purge superhero
DELETE will help us to purge the superhero. Let's check it we can delete .
@Test
@Order(4)
void delete() {
var request = HttpRequest.DELETE("/rx/superhero/1");
var response = client.toBlocking().exchange(request);
Assert.assertEquals(HttpStatus.NO_CONTENT, response.getStatus());
}
Now we have done everything required to write the tests with Micronaut and Testcontainers. To run it we can use /mvnw test
command in the root folder of the micronaut project. Below is the image of the output on my system. You can see the docker creating the Postgres container before tests and tests getting passed.