How to test EntityManager query with H2 in memory DB - java

I have a Spring Boot project and I want to test some queries. I want to insert a predefined set of data and execute the Repository query to check the result is the desired one.
To do this I'm using in-memory H2 DB and the problem (I think) is not there, everything related with DB is ok. The main problem is I can't mock properly the EntityManager field in repository and the query is always null.
My repository is like this:
#Repository
public class MyRepositoryImpl implements MyRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public Result runQuery() {
TypedQuery<Result> query = entityManager.createQuery(
"SELECT ...", Result.class);
return query.setParameter("...", "...") // here 'query' is always null
.setMaxResults(1)
.getResultStream()
.findFirst()
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Entity not found"));
}
}
It works nice when is executed out of tests, but trying to run this test file it throw an error:
#RunWith(SpringRunner.class)
public class MyRepositoryTest {
#Mock
EntityManager entityManager;
#InjectMocks
MyRepositoryImpl repository;
#Test
public void it_should_works() {
Result r = repository.runQuery();
assertNull(r);
}
}
The repository is mocked and is not null, I can call the method. But inside the repository, as query field is null, it throw a NullPointerException when try to execute.
I've searched over the internet and I've found many ways to test the JPARepository and #Query inside the interface, but not an EntityManager query.
Also I've found a few ways to mock the result for the query, something like when(runQuery()).thenReturn(result) but I don't want that, I've the data in the memory DB so I want to execute the query and get the result.
So, now, the main problem I think is how to mock the EntityManager object properly inside repository class.
Thanks in advance.
Edit:
I've follow this link and is like another SO questions: It's only to mock the JpaRepository.
I've used this code:
#Test
public void it_should_works() {
Result r = repository.findAll();
assertNotNull(r);
}
And works perfectly, but using my own query fails with error:
org.springframework.orm.jpa.JpaSystemException: could not advance using next(); nested exception is org.hibernate.exception.GenericJDBCException: could not advance using next()
...
Caused by: org.h2.jdbc.JdbcSQLNonTransientException: El objeto ya está cerrado
The object is already closed [90007-200]
So the question is: It is related to my DB? Why using JpaRepository method it works but my own query don't?
Edit:
Solved adding #Transactional into repository.

Since you're using an h2 in-memory database to run tests, and you want to actually use that database in your tests, you shouldn't really be mocking anything.
Your mocking doesn't work because the MyRepositoryImpl is typically initialized by Spring, and that process is much more complicated than plugging in an EntityManager.
I think what you want to do is more like what's described here https://www.baeldung.com/spring-testing-separate-data-source
So you would have a src/test/resources/application.properties file that overrides the datasource properties. Then you just #Autowired your repository into your test class like normal.

Related

Unable to Mock EntityManager Criteria API

I want to write the junit for EntityManager but unable to mock the EntityManager. My service class method listed below:
#PersistenceContext
private EntityManager em;
public List<Items> getItems() {
Order order = Order.desc("ASC");
Criteria crit = em.unwrap(Session.class).createCriteria(Items.class);
Criteria critRowCount = em.unwrap(Session.class).createCriteria(Items.class);
crit.add(Restrictions.eq("createdBy", "John"));
critRowCount.add(Restrictions.eq("createdBy", "John"));
crit.setFirstResult((pageNo - 1) * pageSize);
crit.setMaxResults(pageSize);
crit.addOrder(order);
List<Items> items = crit.list();
critRowCount.add(Restrictions.sqlRestriction("Query"));
critRowCount.setProjection(Projections.rowCount()).uniqueResult();
return items;
}
I used in my tast case:
#MockBean
private EntityManager em;
and when I called below method in service
List<Items> items = crit.list();
This will hit DB. Please halp me to resolve the problem.
Thanks in Advance!
I have no idea why the mocking does not work (from what you present here, I'd say it should work). But I would like to suggest another approach. I suppose the getItems() method resides in some kind of Repository or DAO or whatever you call this kind of thing. For unit-tests I would usually mock them. If I would want to test getItems(), I would actually try to test it through the database (e.g. via testcontainers) in an integration test, because this rather close to the database anyway.
In your concrete case I would probably not even try to test getItems(), since it only seems to contain some plumbing code.
As a rule of thumb: It is usually not a good idea to mock what you do not own.

#DataJpaTest saving entity using repository, can't retrieve the data using JdbcTemplate

I am migrating from spring-boot 1.5.x to 2.0.4 and noticed an interesting case/behaviour with #DataJpaTest
I have a test
#ExtendWith(SpringExtension.class)
#DataJpaTest
public class SomeTest {
#Autowired
private SomeRepository repository;
#Autowired
private JdbcTemplate template;
#Test
public void save() {
String number = "123";
SomeEntity entity = SomeEntity.builder()
.number(number)
//some attributes here
.build();
repository.save(entity);
//that line fails because SELECT returns nothing back
Map<String, Object> result = template.queryForMap("select id, version from some_entity where number=?", number);
}
This test above fails because nothing is returned from the template. I can't see even INSERT statement fired in the logs.
But with old version of spring-boot of 1.5.x I can see the INSERT been fired and test passes.
But the interesting thing with upgraded version is if I add a line repository.findAll (not repository.findById(id) -> that one won't help) before repository.save(entity) it all works good and in the logs, I can see and INSERT statement fired.
Can someone please help me to understand what's going on and how it works under the hood.
Why the entity has not been persisted? and why repository.findAll is so special that makes it to persist the data.
Thanks.
It happens because findAll method triggers flush of all changes from memory(persistence context) to database.
You can see in my screen the place where it all happens:
There is also a corresponding method in JpaRepository:
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#flush--
It may be used to flush changes immediately.
Additionally, you may use saveAndFlush method:
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#saveAndFlush-S-

JUnit: how to test method which deletes entity (JPA)

I have simple crud repository:
public interface BookRepository extends CrudRepository<Book, Long> {
#Modifying
#Transactional
void deleteById(Long id);
}
How can I write simple JUnit test to check if this method works fine? Of course, in this case, I have entity Book
You should not.
Unit testing Spring Data repositories is a problem of Spring Data developers - as a developer you are using their API and it should working that's all. Care about your logic not the library - focus on integration tests
Anyway if you really want to do this - what I would do would be to mock EntityManager and somehow inject it to the Repository implementation (sounds like horrible task), or I would use DBUnit test, prepare test data and simply would check whether object was deleted (this would be prefered way)
Usually Spring Data repositories do not tests, but If you really want to test it you can use #DataJpaTest and inject TestEntityManager.
Example:
#DataJpaTest
class BookRepositoryTest {
#Autowired
private BookRepository repository;
#Autowired
private TestEntityManager em;
#Test
void deleteById() {
Book book = ...;
final Long id = em.persistAndGetId(book, Long.class);
repository.deleteById(id);
em.flush();
Book after = em.find(Book.class, id);
assertThat(after).isNull();
}
}
Idea is that you use TestEntityManager for all operation exclude your. In the example you are persist and find entity via TestEntityManager but delete via your custom repository.

Spring boot + Hibernate + JPA No transactional EntityManager available

I am using spring boot 1.2.3.RELEASE version with JPA over hibernate. I am experiencing following exception
org.springframework.dao.InvalidDataAccessApiUsageException: No transactional EntityManager available; nested exception is javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410) ~[EntityManagerFactoryUtils.class:4.1.6.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223) ~[HibernateJpaDialect.class:4.1.6.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417) ~[AbstractEntityManagerFactoryBean.class:4.1.6.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[ChainedPersistenceExceptionTranslator.class:4.1.6.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[DataAccessUtils.class:4.1.6.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[PersistenceExceptionTranslationInterceptor.class:4.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122) ~[CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.class:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) [ExposeInvocationInterceptor.class:4.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) [JdkDynamicAopProxy.class:4.1.6.RELEASE]
at com.sun.proxy.$Proxy110.deleteByCustomerId(Unknown Source) ~[na:na]
Caused by: javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:275) ~[SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.class:4.1.6.RELEASE]
at com.sun.proxy.$Proxy102.remove(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$DeleteExecution.doExecute(JpaQueryExecution.java:270) ~[JpaQueryExecution$DeleteExecution.class:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74) ~[JpaQueryExecution.class:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:97) ~[AbstractJpaQuery.class:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:88) ~[AbstractJpaQuery.class:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:395) ~[RepositoryFactorySupport$QueryExecutorMethodInterceptor.class:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:373) ~[RepositoryFactorySupport$QueryExecutorMethodInterceptor.class:na]
Following is my program structure
Configuration class
#Configuration
#ComponentScan
#EnableAutoConfiguration
#EnableTransactionManagement
public class WSApplication {
public static void main(final String[] args) {
SpringApplication.run(WSApplication.class, args);
}
}
#Entity
#Table(Orders)
public class Order {
#id
#GeneratedValue
private long id;
#Column(name = "customerId")
private Long customerId;
// getter & setter methods
// equals & hashCode methods
}
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
// 4- #Transactional works fine
void deleteByCustomerId(Long cusotmerId);
}
public class OrderService {
#Autowired
private OrderRepository repo;
// 3- #Transactional works fine
public void deleteOrder(long customerId){
//1- throws exception
repo.deleteByCustomerId(customerId);
//2- following works fine
//repo.delete(repo.findByCustomerId(customerId).get(0));
}
}
In above service class code, can anyone please guide me why 2 works and 1 throws exception.
Thanks
First, I make a quote of the Spring-Data JPA Documentation to justify why the delete method works in your case (I mean the option 2).
CRUD methods on repository instances are transactional by default. For
reading operations the transaction configuration readOnly flag is set
to true, all others are configured with a plain #Transactional so that
default transaction configuration applies. For details see JavaDoc of
CrudRepository
The delete method is actually a method of the CrudRepository. Your repository extends JpaRepository which extends CrudRespository, so it belongs to CrudRepository interface and according the quote above is transactional.
If you read the section Transactional Query Method you will see that is the same that the option 4 and you will know how to apply a custom transactional behavior for all methods of your repository.
Also, the Example 61 of the documentation shows the same scenario that the option 3.
Now keep in mind that you aren't working with JDBC logic, in which case the database take care about the transactions, but within a ORM-based framework. ORM frameworks require a transaction in order to trigger the synchronization between the object cache and the database.
So you must be aware and provide a transaction context for methods that do ORM logic like deleteByCustomerId.
By default #Transactional (I mean without any parameter) set propagation mode to REQUIREDand readOnly flag to false. When you invoke a method annotated within, a transaction is intialized if no-one exists. This is the reason of why the workaround of #LucasSaldanha (the same as example Using a facade to define transactions for multiple repository calls) and the option 4 works. Other wise, without a transaction, you fall in the thrown exception of the option 1.
Ok I found out a way of making it works.
Just put a #Transactional annotation (org.springframework.transaction.annotation.Transactional) in your deleteOrder method at OrderService.
#Transactional
public void deleteOrder(long customerId){
repo.deleteByCustomerId(customerId);
}
I really don't know why the second works. I guessing that since it is an direct method from the CrudRepository interface someway it knows how to execute it atomically.
The former one is a call to the deleteByCustomerId. This call will be processed to find out the customer with the specified id and then deletes it. For some reason it makes the use of an explicit transaction.
Again it is just a guess. I'll try to contact some spring developers and maybe open a issue to verify this behaviour.
Hope it helps!
Reference: http://spring.io/guides/gs/managing-transactions/
I still got the No transactional EntityManager available exception even after annotating my search() method with #Transactional.
I followed this tutorial which describes how to set up Hibernate search in Spring Boot.
The issue for me was that I had a different dependency on hibernate-search-orm. The dependency which worked for me without any problems was
compile("org.hibernate:hibernate-search-orm:5.7.0.Final")
After adding this to the gradle build file, everything worked as expected.
Hope this helps someone else as well.
SimpleJpaRepository class, which is the default class that Spring uses to provide implementations for repository interfaces.
And if you check the class you can see the function that is being called is having #Transactional
#Override
#Transactional
public void deleteAllById(Iterable<? extends ID> ids) {
Assert.notNull(ids, "Ids must not be null!");
for (ID id : ids) {
deleteById(id);
}
}

JUnit with hibernate: this instance does not yet exist as a row in the database

I'm doing a test by using JUnit.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:com/mt/sm/application-context.xml", "classpath:com/mt/sm/security-context.xml"})
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#Transactional
public class LocationPathServiceImplTest { /* Class code here */ }
The test method declaration is quite simple:
#Test
public void testRefresh() { /* Method body */}
I've created save an obj in the setup() and save to db.
While in #Test I run the refresh() from DAO (the refresh() method just calls EntityManager .refresh()), but it causes me the error below
org.hibernate.HibernateException: this instance does not yet exist as a row in the database
javax.persistence.PersistenceException: org.hibernate.HibernateException: this instance does not yet exist as a row in the database
I have no idea how to fix it. Did anybody come across this before?
All suggestions would be appreciated.
At no point I commit the changes to the database nor I call .flush(). For my best understanding, they are within the current transaction.
Without more code I'd say, you need to flush your DAO so the instance gets persisted. refresh is only object level while flush does an actual transaction on database level (hence the rollback = true so it gets rolled back after the test)
I'm not sure the other answers saying you should flush() are correct as this will not commit anything to the database. See Hibernate docs. Flushing the Session simply gets the data that is currently in the session synchronized with what is in the database. So your exception makes sense if you have not called myobject.save() in your setUp() method.
I don't think you want to call commit() anywhere either becasue you want everything to rollback after your test completes. Use these annotations on your Class
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#Transactional
You can then add #before on your setUp() method although if your class extends TestCase this will be the same. Thor84no is correct in that the #before method will execute in the same transaction as your #Test method. If you actually wanted to seed the database with commited data you can use a method annotated with #beforeTransaction instead.
[EDIT]
Based on your updated question, it sounds like you have not called persist() or similar on the object you say you have created in setup() and it's considered to be dettached (i.e. not persisted to the database within your transaction).
I would also flush / close / reopen session to force actual writing into database.

Categories

Resources