Why is my data not persisted/accessible in an Spring-Boot integration-test with HTTPGraphQLTester and TestEntityManager - postgresql

I have a bare-bones Spring-Boot app with some GraphQL endpoints and a Postgres database and want to run an integration test against an endpoint. It should find an entity by its ID and does so without a problem when I send a request manually via Postman. However when I write an integration test for the controller it doesn't. The data seems to be saved after using
TestEntityManager (or the JpaRepository directly) an I get the entity back with its ID. I then stick that ID into a query with HttpGraphQlTester which fails with an empty result/null. I traced it with the debugger and discovered that when the endpoint calls the repository to retrieve the entity with the given ID it gets null or when I look at all the repo-contents it's just an empty list. So my data seems to be accessible in my test but not in my repo/service. Any pointers would be very much appreciated.
Test
#SpringBootTest
#AutoConfigureHttpGraphQlTester
#AutoConfigureTestEntityManager
#Transactional
public class BackboneTreeControllerTest {
#Autowired
HttpGraphQlTester tester;
#Autowired
private TestEntityManager testEntityManager;
#Test
void findTaxon() {
Taxon taxon = Taxon.builder()
.path(Arrays.asList("path", "to", "taxon"))
.nameCanonical("Cocos nucifera")
.authorship("Me")
.extinct(false)
.numDescendants(1l)
.numOccurrences(1l)
.build();
Taxon savedTaxon = testEntityManager.persistFlushFind(taxon); // (1)
this.tester.documentName("queries")
.operationName("FindTaxon")
.variable("taxonId", savedTaxon.getId())
.execute()
.path("findTaxon.authorship")
.entity(String.class)
.isEqualTo("Me");
the testEntityManager returns successfully with an ID.
Query
query FindTaxon($taxonId: ID!) {
findTaxon(id: $taxonId) {
authorship
}
}
Controller
#Controller
#AllArgsConstructor
public class BackboneTreeController {
private final TaxonService taxonService;
#QueryMapping
public Taxon findTaxon(#Argument Integer id) {
Optional<Taxon> taxon = taxonService.findTaxon(id);
return taxon.orElse(null);
}
}
Service
#Service
#AllArgsConstructor
public class TaxonService {
private final TaxonRepository taxonRepository;
public Optional<Taxon> findTaxon(Integer id) {
return taxonRepository.findById(id); // (2)
}
}
This is where I would expect the repo to return the entity but it does not. Also using .findAll here returns an empty list.
Repository
#Repository
public interface TaxonRepository extends JpaRepository<Taxon, Integer> {
}
Note that everything works fine when I just run the app and send the exact same query manually!

I don't know HttpGraphQlTester but I'd assume that it generates requests which then get processed in a separate thread.
That thread won't see the changes made in the test because they aren't committed yet.
If this is the reason resolve it by putting the setup in it's own transaction, for example by using TransactionTemplate.

Related

inject a JPA repository in Spring Boot test, without session issue

I am adding some tests on my Spring Boot 2.4 application that works well in production.
In one of my SpringBootTest , I call the API (using mockMvc) and compare the result with what I have in the DB.
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
class TicketIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private TicketTypeRepository ticketTypeRepository;
#Test
void shouldReturnListOfTicketTypes() throws Exception {
RequestBuilder request =
MockMvcRequestBuilders.get(RESOURCE_BASE_URL + "/types").contentType(APPLICATION_JSON);
String responseAsString =
mockMvc
.perform(request)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
List<TicketTypesRepresentation> ticketTypes =
objectMapper.readValue(
responseAsString, new TypeReference<List<TicketTypesRepresentation>>() {
});
assertThat(ticketTypes).hasSameSizeAs(ticketTypeRepository.findAll());
}
}
I have the feeling I've written that type of tests hundreds of times, but on this one, I am facing a problem : my application is configured correctly, because I receive a list of items in the API response.
However, what I find strange is that I get an exception from the ticketTypeRepository.findAll() call :
failed to lazily initialize a collection of role ... could not initialize proxy - no Session
I understand the issue, and I can fix it either by making the relation eager (with #Fetch(FetchMode.JOIN) on the entity), or my making the test #Transactional but I am not sure I like any of the options..
I don't remember facing that issue in the past in other Spring Boot tests so I am a bit puzzled.
Am I missing something to make sure that all the calls made to ticketTypeRepository are made within a transaction ? TicketTypeRepository is a wrapper around a CrudRepository, is it the reason why it doesn't work directly ?
Here's the entity and repository code :
public class JpaTicketTypeRepository implements TicketTypeRepository {
public List<TicketType> findAll() {
var allTicketTypesEntity= jpaTicketTypesEntityRepository.findAll();
return StreamSupport.stream(allTicketTypesEntity.spliterator(), false)
.map(TicketTypeEntity::toTicketTypeList)
.collect(Collectors.toList())
.stream().flatMap(List::stream)
.collect(Collectors.toList());
}
}
and the entity (simplified) :
#Table(name = "TICKET_TYPES")
#Entity
#Slf4j
public class TicketTypeEntity {
#Id
private Long id;
#OneToMany
#JoinTable(name = "TICKET_TYPES_GROUPS",
joinColumns =
{#JoinColumn(name = "TICKET_TYPE_ID", referencedColumnName = "ID")},
inverseJoinColumns =
{#JoinColumn(name = "TICKET_GROUP_ID", referencedColumnName = "ID")})
#Nonnull
private List<TicketGroupsEntity> ticketGroupsEntity;
#Nonnull
public List<TicketType> toTicketTypeList() {
log.info("calling toTicketTypeList for id "+id);
log.info(" with size : "+ticketGroupsEntity.size());
return ticketGroupsEntity.stream().map(group -> TicketType.builder()
.id(id)
.build()).collect(Collectors.toList());
}
}
The exception happens the first time size() is called on the collection :
failed to lazily initialize a collection of role:
my.service.database.entities.TicketTypeEntity.ticketGroupsEntity,
could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role:
my.service.database.entities.TicketTypeEntity.ticketGroupsEntity,
could not initialize proxy - no Session at
org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at
org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162)
at
org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:371)
at
my.service.database.entities.TicketTypeEntity.toTicketTypeList(TicketTypeEntity.java:78)
at
java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at
java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at
java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at
java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at
java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at
java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at
java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at
my.service.database.JpaTicketTypeRepository.findAll(JpaTicketTypeRepository.java:29)
I believe you mis-interpret the stack trace. The problem is not in calling the method size() on the result of findAll(), but in the method findAll itself.
In findAll you call TicketTypeEntity.toTicketTypeList, which converts DB entity to DTO. This method touches ticketGroupsEntity, which is a lazy collection.
The code fails in unit test, but runs when accessed via springs controller.
This is due to Open Session In View, which is enabled by default.
See:
A Guide to Spring’s Open Session In View
The Open Session In View Anti-Pattern
You could solve it multiple ways:
#Transactional findAll (be aware of lazy loading issues)
explicit fetch in query
entityGraph
But to my eyes your entity mapping looks suspicious, you seem to have all data needed to construct TicketType in TicketGroupsEntity. Maybe you could query that entity instead?

Is there a way to log correlation id (sent from microservice) in Postgres

I am setting up the logging for a project and was wondering, whether it is possible to send id to postges database. Later I would collect all logs with fluentd and use the efk(elastic search, fluentd, kibana) stack to look through the logs. That is why it would be very helpful if can set the id in the database logs.
You can have your connection set the application_name to something that includes the id you want, and then configure your logging to include the application_name field. Or you could include the id in an SQL comment, something like select /* I am number 6 */ whatever from whereever. But then the id will only be visible for log messages that include the sql. (The reason for putting the comment after the select keyword is that some clients will strip out leading comments so the serve never sees them)
Thank you #jjane,
for giving me such a great idea. After some googling I found a page describing how to intercept hibernate logs
then I made some more research on how to add the inspector to spring-boot and this is the solution I came up with:
#Component
public class SqlCommentStatementInspector
implements StatementInspector {
private static final Logger LOGGER = LoggerFactory
.getLogger(
SqlCommentStatementInspector.class
);
private static final Pattern SQL_COMMENT_PATTERN = Pattern
.compile(
"\\/\\*.*?\\*\\/\\s*"
);
#Override
public String inspect(String sql) {
LOGGER.info(
"Repo log, Correlation_id:"+ MDC.get(Slf4jMDCFilter.MDC_UUID_TOKEN_KEY),
sql
);
return String.format("/*Correlation_id: %s*/", MDC.get(Slf4jMDCFilter.MDC_UUID_TOKEN_KEY)) + SQL_COMMENT_PATTERN
.matcher(sql)
.replaceAll("");
}
}
and its configuration:
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer {
#Autowired
private SqlCommentStatementInspector myInspector;
#Override
public void customize(Map<String, Object> hibernateProperties) {
hibernateProperties.put("hibernate.session_factory.statement_inspector", myInspector);
}
}

How Can I have multiples instances of a Spring boot Repository(Interface), to have a complete test-state-isolation?

1) Contextualization:
In order, to have a complete test-isolation-state in all test of my Test-Class;
I would like to have a new-instance-repository(DAO) for each individual test;
My Repository is a Interface, thats the why I can not simply instantiate that.
My Goal is:
Run all tests 'Parallelly', meaning 'at the same time';
That's the why, I need individual/multiple instances of Repository(DAO) in each test;
Those multiple instances will make sure that the tests' conclusion would not interfere on those that still is running.
Below is the code for the above situation:
1.1) Code:
Current working status: working, BUT with ths SAME-REPOSITORY-INSTANCE;
Current behaviour:
The tests are not stable;
SOMETIMES, they interfere in each other;
meaning, the test that finalize early, destroy the Repository Bean that still is being used, for the test that is still running.
public class ServiceTests2 extends ConfigTests {
private List<Customer> customerList;
private Flux<Customer> customerFlux;
#Lazy
#Autowired
private ICustomerRepo repo;
private ICustomerService service;
#BeforeEach
public void setUp() {
service = new CustomerService(repo);
Customer customer1 = customerWithName().create();
Customer customer2 = customerWithName().create();
customerList = Arrays.asList(customer1,customer2);
customerFlux = service.saveAll(customerList);
}
#Test
#DisplayName("Save")
public void save() {
StepVerifier.create(customerFlux)
.expectNextSequence(customerList)
.verifyComplete();
}
#Test
#DisplayName("Find: Objects")
public void find_object() {
StepVerifier
.create(customerFlux)
.expectNext(customerList.get(0))
.expectNext(customerList.get(1))
.verifyComplete();
}
}
2) The ERROR happening:
This ERROR happens in the failed-Tests:
3) Question:
How Can I create multiple instances of Repository
Even if, it being a Interface(does not allow instantation)?
In order, to have a COMPLETE TEST-ISOLATION
Meaning: ONE different instance of Repository in each test?
Thanks a lot for any help or idea
You can use the #DirtiesContext annotation on the test class that modifies the application context.
Java Doc
Spring documentation
By default, this will mark the application context as dirty after the entire test class is run. If you would like to mark the context as dirty after a single test method, then you can either annotate the test method instead or set the classMode property to AFTER_EACH_TEST_METHOD at your class level annotation.
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
When an application context is marked dirty, it is removed from the
testing framework's cache and closed; thus the underlying Spring
container is rebuilt for any subsequent test that requires a context
with the same set of resource locations.

Detecting when the database session gets refreshed on a Spring Boot 2 application

I'm trying to execute the following SQL statement every time the Database Session gets refreshed. I have a Spring Boot 2.0.1.RELEASE with JPA application and a PostgreSQL Database.
select set_config('SOME KEY', 'SOME VALUE', false);
As the PostgreSQL documentation states the is_local parameter is used to indicate that this configuration value will apply just for the current transaction -if true- or will be attached to the session (as I require) -if false-
The problem is that I'm not aware when Hibernate/Hikari are refreshing the db session, so, in practice, the application start failing when it has a couple of minutes running, as you can imagine...
My approach -that is not working yet- is to implement a EmptyInterceptor, for that I have added a DatabaseCustomizer class to inject my hibernate.session_factory.interceptor properly in a way that Spring can fill out all my #Autowires
DatabaseInterceptor.class
#Component
public class DatabaseInterceptor extends EmptyInterceptor {
#Autowired
private ApplicationContext context;
#Override
public void afterTransactionBegin(Transaction tx) {
PersistenceService pc = context.getBean(PersistenceService.class);
try {
pc.addPostgresConfig("SOME KEY", "SOME VALUE");
System.out.println("Config added...");
} catch (Exception e) {
e.printStackTrace();
}
}
}
DatabaseCustomizer.class
#Component
public class DatabaseCustomizer implements HibernatePropertiesCustomizer {
#Autowired
private DatabaseInterceptor databaseInterceptor;
#Override
public void customize(Map<String, Object> hibernateProperties) {
hibernateProperties.put("hibernate.session_factory.interceptor", databaseInterceptor);
}
}
Obviously, there is a problem with this approach because when I #Override the afterTransactionBegin method to start another transaction I get an Infinite loop.
I tried to look something inside that Transaction tx that could help to be sure that this transaction is not being generated by my own addPostgresConfig but there is not much on it.
Is there something else I could try to achieve this?
Thanks in advance,

New REST request in JHipster returning "Not Found - 404"

Error Description
Hey all,
I'm having trouble getting a response from my manually added controllers in a JHipster-based project. I scaffolded up the original project, and then hand-wrote my own services and controllers.
When I execute the call, the error result I get from SoapUI (which I am using for initial validation) is at the following url: http://imgur.com/04FpmEZ,Havk1EL#0
And if I look at my Eclipse console error, I see the following: http://imgur.com/04FpmEZ,Havk1EL#1
Controller
/**
* GET /courses/json -> get all the courses.
*/
#RequestMapping(value = "/json",
method = RequestMethod.GET,
produces = "application/json")
#Timed
public List<Course> getAll() {
log.debug("REST request to get all Courses");
return courseService.findAllCourses();
}
Service
package com.testapp.myapp.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.testapp.myapp.domain.Course;
import com.testapp.myapp.repository.CourseRepository;
#Service
#Transactional
public class CourseServiceImpl implements CourseService {
#Autowired
CourseRepository courseRepository;
public long countAllCourses() {
return courseRepository.count();
}
public void deleteCourse(Course course) {
courseRepository.delete(course);
}
public Course findCourse(Integer id) {
return courseRepository.findOne(id);
}
public List<Course> findAllCourses() {
return courseRepository.findAll();
}
public List<Course> findCourseEntries(int firstResult, int maxResults) {
return courseRepository.findAll(new org.springframework.data.domain.PageRequest(firstResult / maxResults, maxResults)).getContent();
}
public void saveCourse(Course course) {
courseRepository.save(course);
}
public Course updateCourse(Course course) {
return courseRepository.save(course);
}
}
What is confusing about this is that I ran the query provided by hibernate directly against my DB, and it returns the record set just fine. Is it possible that the service is being blocked due to some security or authentication constraint auto-loaded by JHipster?
A few issues existed, all related to migrating from Roo into JHipster:
I had built my new Controller class with org.sprinframework.stereotype.Controller's #Controller annotation, rather than #RestController... The original controller annotation was scaffolded up by Spring Roo (which is highly effective at generating services from an existing DB using their DBRE addon, I might add).
After switching over to #RestController, I ran into the second hurdle, which I had originally expected as a JHipster implementation : the service was being blocked due to authentication constraints.
This was fixed by going into com.[projectname].config and updating the SecurityConfiguration.java file, exposing specifically the APIs that I wanted.
Then, I had to make sure Hibernate was getting the full collection of the objects being requested (I had a lot of complex relational entities being built by Roo)... failed to lazily initialize a collection of role...
In the Domain entity, change your #OneToMany annotation as follows:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "courseId", cascade = CascadeType.REMOVE)
Source of answer: Solve "failed to lazily initialize a collection of role" exception
Voila! Functioning, secure-able JSON-based APIs, fully reverse engineered from an existing Postgresql DB, loaded into a prescaffolded Angular front-end.