How to populate database before running tests with Micronaut - java

I'm looking for a way to execute some SQL scripts before my test class is executed. With Spring I can easily annotate my test class (or test method) with the #Sql annotation. I haven't found any particular way to do the same with Micronaut.
The only way I found was to manually populate the data programmatically in the test method itself, but, in my experience, there are times when you have to perform multiple inserts to test a single case.
I've came up with the following code to test a REST controller:
Code
#Validated
#Controller("/automaker")
public class AutomakerController {
private AutomakerService automakerService;
public AutomakerController(AutomakerService automakerService) {
this.automakerService = automakerService;
}
#Get("/{id}")
public Automaker getById(Integer id) {
return automakerService.getById(id).orElse(null);
}
#Get("/")
public List<Automaker> getAll() {
return automakerService.getAll();
}
#Post("/")
public HttpResponse<Automaker> save(#Body #Valid AutomakerSaveRequest request) {
var automaker = automakerService.create(request);
return HttpResponse
.created(automaker)
.headers(headers -> headers.location(location(automaker.getId())));
}
#Put("/{id}")
#Transactional
public HttpResponse<Automaker> update(Integer id, #Body #Valid AutomakerSaveRequest request) {
var automaker = automakerService.getById(id).orElse(null);
return Objects.nonNull(automaker)
? HttpResponse
.ok(automakerService.update(automaker, request))
.headers(headers -> headers.location(location(id)))
: HttpResponse
.notFound();
}
}
Test
#Client("/automaker")
public interface AutomakerTestClient {
#Get("/{id}")
Automaker getById(Integer id);
#Post("/")
HttpResponse<Automaker> create(#Body AutomakerSaveRequest request);
#Put("/{id}")
HttpResponse<Automaker> update(Integer id, #Body AutomakerSaveRequest request);
}
#MicronautTest
public class AutomakerControllerTest {
#Inject
#Client("/automaker")
AutomakerTestClient client;
#Test
public void testCreateAutomakerWhenBodyIsValid() {
var request = new AutomakerSaveRequest("Honda", "Japan");
var response = client.create(request);
assertThat(response.code()).isEqualTo(HttpStatus.CREATED.getCode());
var body = response.body();
assertThat(body).isNotNull();
assertThat(body.getId()).isNotNull();
assertThat(body.getName()).isEqualTo("Honda");
assertThat(body.getCountry()).isEqualTo("Japan");
}
#Test
public void testUpdateAutomakerWhenBodyIsValid() {
var responseCreated = client.create(new AutomakerSaveRequest("Chvrolet", "Canada"));
assertThat(responseCreated.code()).isEqualTo(HttpStatus.CREATED.getCode());
var itemCreated = responseCreated.body();
assertThat(itemCreated).isNotNull();
var responseUpdated = client.update(itemCreated.getId(), new AutomakerSaveRequest("Chevrolet", "United States"));
assertThat(responseUpdated.code()).isEqualTo(HttpStatus.OK.getCode());
var itemUpdated = responseUpdated.body();
assertThat(itemUpdated).isNotNull();
assertThat(itemUpdated.getName()).isEqualTo("Chevrolet");
assertThat(itemUpdated.getCountry()).isEqualTo("United States");
}
}
I could use a method annotated with #Before to populate all the data I need but it would really be nice to be able to use *.sql scripts the way it is possible with Spring. Is there a way to provide such *.sql scripts before the tests are executed ?

TL;DR — Use Flyway.
With Flyway, you can set up and maintain a given database schema (extremely) easily. To your case, any migration script you put under ../test/resources/db/migration/ (or any other default location you set) will be only visible to your tests, and can be executed/run automatically (if configured) any time you run your tests.
Another solution would be to use an in-memory database (but I would stay away from that for real applications). For instance, H2 have a way to specify an "initialization" script and another for data seeding (and such).

Related

How can I override a Spring Data REST method without disabling default implementations

I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.

Best way to explicitly convert POJO into JSON in Spring RestController

I have an existing REST API in a spring boot project that looks somewhat like this:
#GetMapping(value = "/api/projects")
public List<Project> getProjects() {
List<Project> projects = projectsRepository.findAll();
List<Project> processed = processProjects(projects);
return processed;
}
When this method is called the returned JSON response looks something like this:
{
"JSON":"[
{
"id":"aaa",
"simple":"SYMBOLIC_VALUE_BBB",
"nested1":{
"field1":"SYMBOLIC_VALUE_C1",
"field2":"nonSymbolicValueC2",
"field3":"SYMBOLIC_VALUE_C3"
},
"nested2":{
"fieldA":"SYMBOLIC_VALUE_DDD"
}
},
...
]",
"mode":"application/json"
}
The symbolic values are being translated into a human readable form in the frontend. Everything works fine. But now I also need a second version of this method that does the translation on the backend side. Something like this:
#GetMapping(value = "/api/v2/projects")
public String getProjects() {
List<Project> projects = projectsRepository.findAll();
String projectsAsJson = ???
String processedJson = processProjectsJson(projectsAsJson);
return processedJson;
}
What would I put where the three Question Marks (???) are? I want to use the same json serialization that is used automagically by the Spring Framework. It should be robust against any configuration changes that may happen in the future.
Thank you very much.
Add a attribute ObjectMapper in your Controller, use dependency injection to get it, and then use : mapper.writeValueAsString(myObject);
Something like that:
#Autowired
private ObjectMapper mapper;
#GetMapping(value = "/api/v2/projects")
public String getProjects() {
List<Project> projects = projectsRepository.findAll();
String projectsAsJson = mapper.writeValueAsString(projects);
String processedJson = processProjectsJson(projectsAsJson);
return processedJson;
}
Let me know if it is not working.

Annotation Retrieval given the URI

I want to retrieve the annotations of a service ( in particular #RolesAllowed ) given the URI corresponding to the service.
Here an example:
The service:
#GET
#Path("/example")
#RolesAllowed({ "BASIC_USER", "ADMIN" })
public Response foo() {
//Service implementation
}
I want to retrieve { "BASIC_USER", "ADMIN" } given the String "/example".
I use RestAssured for testing so, if possible, I prefer a solution with the latter.
Thank you.
I am not familiar with RestAssured, but I wrote the following Junit test and it works. Perhaps you can adapt it to work with RestAssured.
First the Service:
public class Service {
#GET
#Path("/example")
#RolesAllowed({ "BASIC_USER", "ADMIN" })
public Response foo() {
return new Response();
}
}
And this is the corresponding Junit test:
#Test
public void testFooRoles() throws Exception {
Method method = Service.class.getMethod("foo");
Annotation path = method.getDeclaredAnnotation(javax.ws.rs.Path.class);
assertTrue(((Path) path).value().equals("/example"));
RolesAllowed annotation = method.getDeclaredAnnotation(RolesAllowed.class);
List<String> roles = Arrays.asList(annotation.value());
assertEquals(2, roles.size());
assertTrue(roles.contains("BASIC_USER"));
assertTrue(roles.contains("ADMIN"));
}

how to use spring boot to handle different search?

What's the best practice to create search function in spring boot with spring data jpa?
#GetMapping("/search")
public List<Hotel> getAllByCriteria(#RequestParam MultiValueMap<String, String> criteria) {
if (criteria.containsKey("cityPublicId")) {
String cityPublicId = criteria.getFirst("cityPublicId");
if (criteria.containsKey("amenity")) {
List<String> amenities = criteria.get("amenity");
return svc.findAllByCityAndAmenities(cityPublicId, amenities);
}
return svc.findAllByCity(cityPublicId);
}
//currently only support one amenity filtration
else if (criteria.containsKey("amenity")) {
return svc.findAllByAmenities(criteria.get("amenity"));
}
return null;
}
Currently I have to identify all possible combination of criteria to use corresponding method, Is there a universal way to handle all condition? Or at least not hardcoding all possible combination.
PS: If I want to filter result by multiple amenities, may I use findByAmenitiesContains(set)? Where a Hotel entity has a set of amenity. Do I have to create custom query using #query?
Thanks.
You basically have the following options:
create the query programmatically from the input data using a custom method. This gives you maximum flexibility but also requires the most work.
Use a specification. Almost the same flexibility and almost as much work.
Use query by example. Very little work, limited flexibility.
Regarding the PS: The capabilities of query derivation are well documented.
AFAIR you can use different request payload entities to handle the same endpoint
#GetMapping(path = "/search", params = { "cityId" })
public List<Hotel> getAllByCriteria(ByCityPublicId byCity) {
return svc.findAllByCity(byCity.getCityPublicId())
}
#GetMapping(path = "/search", params = { "cityId", "amenity" })
public List<Hotel> getAllByCriteria(ByCityPublicIdAndAmenity byCityAndAmenitities) {
return svc.findAllByCityAndAmenities(byCityAndAmenitities.getCityPublicId(), byCityAndAmenitities.getAmenitities())
}
#GetMapping(path = "/search", params = { "amenity" })
public List<Hotel> getAllByCriteria(ByAmenity byAmenity) {
return svc.findAllByAmenities(byAmenity.getAmenity());
}

How to search elastic search via Java using ElasticsearchRepository?

I'm creating a Java application using Elastic Search.
Here is the link for my project.
https://github.com/chanakaDe/ensembl-elastic-rest
In this project, I have implemented a rest controller to take data as JSON.
This is the controller class. Now it only has 2 methods. But I need to add some method like this.
#RequestMapping(value = "/find-by/{id}/{param1}/{param2}/{param3}", method = RequestMethod.GET)
public Iterable<Track> findAllWithParams(#PathVariable int id, #PathVariable String param1, #PathVariable String param2, #PathVariable String param3) {
return trackService.someMethodWithParams(id, param1, param2, param3);
}
What I need to do is take some values from user and send them into Elastic server and make a search. I just refered some of these links and got some idea.
https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
TrackService.java and TrackServiceImpl.java are implemented by TrackRepository.java and it's extended by ElasticsearchRepository default class. https://github.com/chanakaDe/ensembl-elastic-rest/blob/master/src/main/java/com/chanaka/book/repository/TrackRepository.java
I need to take values via REST URL and create an object like following and pass that to Elastic Server. How can I implement that with my current project configuration ?
{
"query": {
"constant_score" : {
"filter" : {
"terms" : { "user" : ["kimchy", "elasticsearch"]}
}
}
}
}
This is my TrackService.java interface.
public interface TrackService {
Track save(Track track);
Track findOne(int id);
Iterable<Track> findAll();
}
And also this is my TrackServiceImpl.java class implemented by TrackService.java.
public class TrackServiceImpl implements TrackService {
private TrackRepository trackRepository;
#Autowired
public void setTrackRepository(TrackRepository trackRepository) {this.trackRepository = trackRepository;}
#Override
public Track save(Track track) {
return trackRepository.save(track);
}
#Override
public Track findOne(int id) {
return trackRepository.findOne(id + "");
}
#Override
public Iterable<Track> findAll() {
return trackRepository.findAll();
}
}
Do I need to implement a custom method for that ? Or is there any default methods like findAll() and findOne() ?
Simply pass an object and get the value ?
I think, there's no such existing method and you need to create your own by using QueryBuilders.wrapperQuery(query.toString()) and ElasticsearchTemplate. Just to note, wrapperQuery supports only query not filter. But you can achieve filter context query with constant_score.

Categories

Resources