I want to test hibernate session's save() method using spring testing framework. @Test method is :
@Test
@Transactional
public void testSave() {
User expected = createUser();
getGenericDao().currentSession().save(expected);
User actual = getUser(generatedId);
assertUsersEqual(expected,actual);
}
I want to flush user into database. I want my user to be in database after this method
getGenericDao().currentSession().save(expected);
Then I want to go to database using spring data framework and fetch this saved user by next line:
User actual = getUser(generatedId);
I tried to use hibernate flush method like:
currentSession().setFlushMode(MANUAL);
//do saving here
currentSession().flush();
It does not flush my user into database! However if I don't make use of @Transactional spring annotation and save my user in programmatic spring transaction I achieve what I want. Unfortunately then user saved into db is not rollbacked as there is no spring @Transactional. Therefore my test method changes the db and behaviour of subsequent test methods.
So I need to flush my user into db inside test method(not at the end) and at the end of test method rollback all changes to db.
UPDATE suggestion to prepare method as follows:
@Transactional
public void doSave(User user){
getGenericDao().currentSession().save(user);
}
And call doSave inside testSave is doing nothing. Still I have no user in db after executing this method. I set breakpoint and check my db from command-line.
UPDATE Thanks very much for response. The problem is that method flush() does not put my user into database.I tried Isolation.READ_UNCOMMITTED and it does not put my user into database. I can achieve what I want but only if I turn off spring transaction on @Test method and do saving in programmatic transaction. BUT then @Test method is not rolled back leaving saved user for subsequent @Test methods. Here @Test method to save user is not as dangerous as @Test method to delete user, because it is not rolled back. So there must spring transactional support for @Test method with which I can't anyway put my user(or delete) into db. Actually user is put(or deleted) into db only after @Test method ends and transaction for @Test method is comitted. So I want to save my User into db in the middle of @Test method and roll back it at the end of @Test method
Thank you!
At a high level, when a class declares @Transactional on itself or its members, Spring creates a proxy that implements the same interface(s) as the class you're annotating. In other words, Spring wraps the bean in the proxy and the bean itself has no knowledge of it.
Instead, you now need to do two things: Make sure that your Spring Configuration is annotated with the @EnableTransactionManagement annotation (In Spring Boot this will be done automatically for you). Make sure you specify a transaction manager in your Spring Configuration (this you need to do anyway).
That means flush() will not make current changes visible to other EntityManager instances or other external database clients; that will only happen at the transaction commit. In other words flush() operation will only flush the current memory cache from EntityManager to the database session.
The FlushMode defines when your persistence provider flushes new and changed entities to the database. Based on the JPA specification, it can either do that automatically before executing a query and before committing the transaction (FlushModeType. AUTO) or only before committing the transaction (FlushModeType.
Finally I stuck to the following solution:
First, my @Test
methods are not running within spring @Transactional
support. See this article to know how dangerous it may be. Next, instead of using @Repository
beans inside @Test
methods I autowire @Service
beans which use @Transactional
annotation.
The miracle is that @Test
method like this
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testSave() {
Answer created = createAnswer();
Long generatedId = answerService.save(created);
//at this moment answer is already in db
Answer actual=getAnswerById(generatedId);
... }
puts my Answer object into database (just after answerService.save(created);
) and method getAnswerById
goes to DB and extracts it to check if save was correct.
To eliminate changes made to database in @Test
method I recreate database by JdbcTestUtils.executeSqlScript
@Transactional
tests (Spring Pitfalls: Transactional tests considered harmful). I've used @org.springframework.test.context.jdbc.Sql
to re-populate DB in my service tests and @Transactional
for controllers.ConstraintViolationException
for controller update test with invalid data have been thrown only when transaction is committed. So I've found 3 options:
@Commit
or with @Transactional(propagation = Propagation.NEVER)
. Be aware of DB change.TestTransaction
Code:
TestTransaction.flagForCommit();
TestTransaction.end();
TransactionTemplate
Code:
@Autowired
private PlatformTransactionManager platformTransactionManager;
@Test(expected = Exception.class)
public void testUpdate() throws Exception {
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
String json = ...
transactionTemplate.execute(ts -> {
try {
mockMvc.perform(put(REST_URL + USER_ID)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk());
...
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With