Micronaut With Relation Database and...Tests
This article is a Micronauts HTTP server with a relation database persistence layer. We created this focussing on the tests at the controller layer.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
I am writing this article as I was starting to read about Micronauts.
I have been working with spring-boot over the past years, which seems very complete from my point of view. But, as I was hearing about the Micronauts project I wanted to go through and compare what I would find there with my current experience.
All the code described here is committed on my GitHub:
https://github.com/naspredam/rest-micronaut-reactive-java-users
What Is Micronaut?
You can find a lot of literature on Micronauts, and have a lot of features:
https://micronaut.io/documentation.html
To simplify, as I am not pretending to go through all Micronauts offers... As a basic, this framework aims to:
- Can be written in: Java (chosen on this article), Kotlin and Groovy
- Compile-time AOP and dependency injection (same mind-set as spring)
- Reactive HTTP client and server
- A suite of cloud-native features
What I will focus on this article is just an HTTP server with a relational database. We are going to do a CRD service where we have the domain object UserData, where we want (the U is not implemented on the code, but it is really straightforward):
- GET: all of them
- POST: create one
- DELETE: one
- GET: get one by id
Create the Service
When I started to create the project, I saw that it was a command-line with SDK. It seems to be the first option of the documentation, and the command line is very complete. However, there is also the link:
Which is pretty much as spring does with https://start.spring.io/.
I liked more doing on the link, but you can create the service using command line, up to you.
What We Were Looking for
What I am going to create is a service that it is very common, plain:
- Java language
- API HTTP
- JPA (Java Persistent API)
- MySQL (relational database)
- Gradle
This was quite straightforward to do, as with the URL above we have it all, just need select application and then on dependencies look for:
- Micronaut-hibernate-JPA
- MySQL
and add them on your project. Then finally you will download it in a zip file.
Implementation
When we go to implement we have to:
- Create the code to make our application work (obviously...)
- Create tests to make sure that the application will continue working
It is quite common that the second point is less obvious :) However, when doing TDD the order of the steps are the other way around as the test should be the first step.
Let's Start With Coding?
What I am going to present this is to create the service based on the tests. As it would be boring to do baby steps on the article, what I have organized is based on milestones, where I am going to present the steps that has some major change.
There are three milestones, where the third one we would have the service running and returning something, based on the database.
Milestone 1 — Doing a Dummy Endpoint
If we try with the test, we can do the following, thinking to start with and endpoint GET to get all users:
x
package com.service.micronaut.users;
import io.micronaut.core.type.Argument;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
import java.util.List;
import static io.micronaut.http.HttpRequest.*;
import static org.assertj.core.api.Assertions.assertThat;
public class UserControllerTest {
"/") (
private RxHttpClient client;
public void shouldReturnEmptyListWhenNoInformationPersisted() {
HttpResponse<List<UserData>> usersResponse = client.toBlocking()
.exchange(GET("/users"), Argument.listOf(UserData.class));
assertThat(usersResponse.code()).isEqualTo(200);
assertThat(usersResponse.body()).isEmpty();
}
}
Here we can see that we have:
- @MicronautTest: Which acts as @SpringBootTest starting the application on test mode
- RxHttpClient: A real client that calls the endpoint, for later to validate the response
- I am using here a third party library for assertions: Assertj. This is really optional...I could use direclty JUnit itself. You can see on github about the dependency used.
The DTO corresponds to:
x
package com.service.micronaut.users;
import lombok.*;
public class UserData {
private Long id;
private String firstName;
private String lastName;
private String phone;
}
We used Lombok for avoiding to write too much code, even though we have quite a lot of annotations... this is also optional. This object will become our JPA entity (I am not going to separate presentation and persistence layers, to make this simpler).
With that, the minimum code we need is:
xxxxxxxxxx
package com.service.micronaut.users;
import io.micronaut.http.annotation.*;
import java.util.List;
"/users") (
public class UserController {
public List<UserData> getAllUsers() {
return List.of();
}
}
Milestone 2 — Create Repository (Controller Dependency)
Now, I would like to go to the next step when we have a dummy repository, so we will add a dependency to the controller (the service architecture will be just controller/repository layers to have a very simple solution):
xxxxxxxxxx
package com.service.micronaut.users;
import io.micronaut.transaction.annotation.ReadOnly;
import javax.inject.Singleton;
import java.util.List;
public class UserRepository {
public List<UserData> findAll() {
return List.of();
}
}
Now the controller will need to have:
x
package com.service.micronaut.users;
import io.micronaut.http.annotation.*;
import java.util.List;
"/users") (
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<UserData> getAllUsers() {
return userRepository.findAll();
}
}
We really not done much, but we added a dependency, and now I have to ways proceed with test:
- Unit test
- Integration test
As the repository is dummy, we can focus on the unit test, later on when we have database we will focus on the integration tests. So that, at a unit level, we should mock the final class dependency behaviour:
x
package com.service.micronaut.users;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.annotation.MockBean;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import javax.inject.Inject;
import java.util.List;
import static io.micronaut.http.HttpRequest.*;
import static org.assertj.core.api.Assertions.assertThat;
public class UserControllerTest {
"/") (
private RxHttpClient client;
private UserRepository userRepository;
UserRepository.class) (
public UserRepository mockUserRepository() {
return Mockito.mock(UserRepository.class);
}
public void shouldReturnEmptyListWhenNoInformationPersisted() {
Mockito.when(userRepository.findAll()).thenReturn(List.of());
HttpResponse<List<UserData>> usersResponse = client.toBlocking()
.exchange(GET("/users"), Argument.listOf(UserData.class));
assertThat(usersResponse.code()).isEqualTo(200);
assertThat(usersResponse.body()).isEmpty();
Mockito.verify(userRepository).findAll();
}
}
Here we have the Micronauts annotation @MockBean. This annotation enables us to mock the implementation of the dependency.
It is interesting that on spring-boot there is the same annotation, however there it automatically creates a Mockito instance of the tagged dependency. Here, on the other hand, it is more flexible, as we could create a new fresh instance implementation, not needing any third-party dependency. On this approach, it seems needed to have an interface, then we could create the new instance based on the interface.
This was a very common practice on spring, some time ago, on the projects that I have been working, it seems that the team considers this interfaces more an over-engineering, not seeing much value on this. Then I decided to create the Mockito mock instance, which it is what I am used to, and mockito give me some methods to have different behaviours per test and also verifiers. Again this is nor mandatory, but my choose.
There is another options to use @Replaces, however the @MockBean, seemed more close from what I am used to.
Milestone 3 — Add Entity Manager
On this milestone, we are going to create the real repository (I am not going to unit test this class, and this will be covered with the integration test, on a real project I would do integration test on this class to have the tests against a real database).
What we have to do is to add the EntityManager on our repository:
xxxxxxxxxx
package com.service.micronaut.users;
import io.micronaut.transaction.annotation.ReadOnly;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import java.util.List;
public class UserRepository {
private final EntityManager entityManager;
public UserRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
public List<UserData> findAll() {
return entityManager.createQuery("select u from UserData u", UserData.class)
.getResultList();
}
}
Here I am going directly to the JPA EntityManager, however, you could define other third party libraries, such as queydsl.
At this point, the unit test we created on the controller stays as it is. On the other hand, we can create an integration test of the controller, which will be from the API call to the database. We are going to use h2 for testing. To do so, we have to add the h2 dependency on our Gradle:
xxxxxxxxxx
testRuntimeOnly("com.h2database:h2")
Then on the application yml (I create a brand new on the test/resources, you could use also application-test.yml):
xxxxxxxxxx
datasources
default
url jdbc h2 mem devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
driverClassName org.h2.Driver
username sa
password''
jpa
default
properties
hibernate
hbm2ddl
auto update
And we have to add the column names on the data object:
xxxxxxxxxx
package com.service.micronaut.users;
import lombok.*;
import javax.persistence.*;
name = "users") (
public class UserData {
strategy = GenerationType.IDENTITY) (
private Long id;
name = "first_name") (
private String firstName;
name = "last_name") (
private String lastName;
name = "phone") (
private String phone;
}
With that, we are ready for our integration test:
xxxxxxxxxx
package com.service.micronaut.users;
import com.github.javafaker.Faker;
import io.micronaut.core.type.Argument;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import java.util.List;
import static io.micronaut.http.HttpRequest.*;
import static org.assertj.core.api.Assertions.assertThat;
public class UserControllerIntegrationTest {
private static final Faker FAKER = new Faker();
"/") (
private RxHttpClient client;
private EntityManager entityManager;
public void shouldReturnTheUserPersisted() {
UserData userData1 = UserData.builder()
.firstName(FAKER.name().firstName())
.lastName(FAKER.name().lastName())
.phone(FAKER.phoneNumber().phoneNumber())
.build();
UserData userData2 = UserData.builder()
.firstName(FAKER.name().firstName())
.lastName(FAKER.name().lastName())
.phone(FAKER.phoneNumber().phoneNumber())
.build();
entityManager.persist(userData1);
entityManager.persist(userData2);
entityManager.getTransaction().commit();
List<UserData> usersResponse = client.toBlocking()
.retrieve(GET("/users"), Argument.listOf(UserData.class));
assertThat(usersResponse).hasSize(2)
.containsExactlyInAnyOrder(userData1, userData2);
}
}
On test code, we do not have anything new from micronaut. It was introduced just java faker, which it is totally optional, to have more randomised test data.
Here the only pain point, I faced, is the need to commit the transaction. From my point of view, this is something that is better handled, as you do not have to commit anything, all stays on the same transaction. However, on this integration test the RxHttpClient is opening another thread, and when the HTTP request reaches the repository, it is in a different transaction as we are inserting the values... then if we do not commit, the result will be always an empty list.
You will find on the documentation that the annotation @MicronautTest rollback after every test. However, when we have to commit, there is nothing to rollback...Then we may have the h2 dirty with data from one test to the others.
Milestone 4 — Add All Wanted Operations
The other methods GET/POST/DELETE is really straightforward. The only thing that I may highlight is that on the non-read-only queries we have to use the annotation:
xxxxxxxxxx
But this is very organic from milestone 3.
All final code can be found at https://github.com/naspredam/rest-micronaut-reactive-java-users/
Conclusions
To sum up, I did not fall in love with this framework.
It seems to me that spring-boot is a little more advanced for development needs on API HTTP with JPA server point of view and with its tests.
Here, I list some points that I feel about Micronauts, on this experience:
- I feel that spring is much better/stronger documented
- I just saw @MicronautTest annotation for testing on Micronauts. In spring-boot I have more options for specific situations, which I feel this useful
- The commit on the transaction to be able to test the endpoint on a integration test... this is something that I could live with, but it seems tricky to have to manually clean up h2... I personally find spring-boot more elegant way to deal with this
- @MockBean annotation is kind of weird to have to create a method... I feel that spring-boot if more organic.
- Micronaut does not offer a simple way to deal with queries, as spring-boot jpa does with JpaRepositories, which it is something that I missed
- Spring offers the MockMvc, which helps to assert the responses, but I also know that we could use the library rest assured, to cover a similar approach.
If I have to decide to choose between those two, it is very likely that I would go to spring-boot.
However as a future step, I would like to see what is the performance of this framework, as this could be an important point another aspect, that was not addressed on this article.
Also, just say that this article is a very concrete example, there is muuuuuch more to see from this framework.
Hope this is helpful.
All the best!
Opinions expressed by DZone contributors are their own.
Comments