Using Junit 4's Timeout #Rule with Spring's AbstractTransactionalJUnit4SpringContextTests - spring

When i use Junit's org.junit.rules.Timeout with spring's base class AbstractTransactionalJUnit4SpringContextTests, i get this exception:
org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
The log output shows:
2010-07-20 09:20:16 INFO [TransactionalTestExecutionListener.startNewTransaction] Began transaction (1): transaction manager [org.springframework.orm.jpa.JpaTransactionManager#6a1fbe]; rollback [true]
2010-07-20 09:20:16 INFO [TransactionalTestExecutionListener.endTransaction] Rolled back transaction after test execution for test context [[TestContext#17b60b6 testClass = MyIntegrationTest, locations = array<String>['classpath:/context.xml', 'classpath:/junit-context.xml'], testInstance = MyIntegrationTest#10a4d7c, testMethod = myTest#MyIntegrationTest, testException = org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress]]
Here is my test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/context.xml", "classpath:/junit-context.xml"})
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#Transactional
public class MyIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests{
#Rule public Timeout globalTimeout = new Timeout(30000);
#Test
public void myTest() {
// transactional code here saving to the database...
}
}
However whenever i comment out the rule, it all works fine.
How can i marry these two together to work correctly?

Ahh, i worked it out. The way i solved it was to setup the transaction programatically.
#Autowired TransactionManager transactionManager;
#Test
public void test() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
status.setRollbackOnly();
// DO YOUR TEST LOGIC HERE
}
});
}
Hope it helps.

LOL.
You can simply also annotate your test method with #Transactional(timeout = 30) for a 30 second timeout. Which is a lot simpler.

Related

How to store data to MariaDB at beforeEach method in Spring boot tests?

I cannot write data to db in #beforeEach as lifecycle methods are not transactional. How can I force data to commit? Data is stored in a transaction, but it is executed after the tearDown() method. By the way, I use MariaDB test container.
#SpringBootTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = TestConfigurations.Initializer.class,
classes = {Application.class, TestConfigurations.class})
#Transactional(transactionManager = "transactionManager")
public class SomeTest {
#Autowired
private SomeRepository someRepository;
#Nested
class SomeNestedClass {
#BeforeEach
void setUp() {
someRepository.saveAll(Fixtures.getSomeEntities());
}
#AfterEach
public void tearDown() {
someRepository.deleteAll();
}
...
Your test methods annotated with #Transactional will be rollback by default by Spring Test, so you can just initialize your data at the beginning of your test.
try using #BeforeTransaction
/ #AfterTransaction

Call original method on a #Spy then throw an exception

I have a #Transactional method that I must test when the transaction fails after it is called, for example :
#Service
public class MyService {
#Transactional
public void myMethod() {
// [...] some code I must run in my test, and throw an exception after it has been called but before the transaction is commited in order for the transaction to be rolled back
}
}
Here is the test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
public class MyServiceTest {
#SpyBean
private MyService myService;
#Test
public void testMyMethod() {
doAnswer(/* some code which would call the real method, then throw an exception in order to cancel the transaction */)
.when(myService).myMethod();
// [...] other code that test other services when that service failed in the transaction but after its real method has been correctly executed
}
}
Could you tell me what code to put in the /* ... */ part in my test?
You can simply use the invocation.callRealMethod() then throw some exception inside the doAnswer:
#Test
public void testMyMethod() {
doAnswer(invocation -> {
invocation.callRealMethod();
throw new IllegalStateException();
})
.when(myService).myMethod();
// [...] other code that test other services when that service failed in the transaction but after its real method has been correctly executed
}

How I can use transactions in TestNG #BeforeClass?

I use TestNG + Spring + hibernate.
When I use transaction in #BeforeClass, I get:
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
Code example:
#Transactional(transactionManager = "transactionManager")
#Rollback
#ContextConfiguration(locations = "/WEB-INF/testing/applicationTestContext.xml")
#TestExecutionListeners(listeners = {
ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class
})
public abstract class ExampleOfTest extends AbstractTestNGSpringContextTests{
#Autowired
private SessionFactory sessionFactory;
#BeforeClass
public void setUpClass() {
sessionFactory.getCurrentSession().beginTransaction(); // get HibernateException
sessionFactory.getCurrentSession().getTransaction().commit();
}
....
}
How I can use transaction in #BeforeClass?
I want to use this for one-time data entry used in all class tests.
Problem would be #EnableTransactionManagement should be in your spring context
or
Try something like
// BMT idiom with getCurrentSession()
try {
UserTransaction tx = (UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
tx.begin();
// Do some work on Session bound to transaction
sessionFactory.getCurrentSession().persist(...);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e; // or display error message
}
getCurrentSession is like restricted, it should run in a active transaction.
I think this may help.

JPA. No transactional EntityManager available

I'm use JPA (Hibernate 4 vendor) and Spring 3.2.x.
I use this code for get Session and re-attach my detached entity.
Session session = entityManager.unwrap(Session.class);
My code look like this :
#Service
public class SchedulerServiceImpl implements SchedulerService {
#PersistenceContext
private EntityManager entityManager;
#Override
#Transactional
#Scheduled(fixedDelay = 5000)
public void executeTasks() {
.. code ..
while (tasksIterator.hasNext()) {
SchedulerTask readyTask = recalculation(currentTask);
}
.. code ...
}
#Override
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public Deposit recalculation(SchedulerTask schedulerTask) {
boolean asda = entityManager.isOpen(); // get TRUE
Session session = entityManager.unwrap(Session.class); // Exception here
session.update(schedulerTask);
... code ...
}
}
What's wrong?
error :
00:21:52,180 ERROR [org.springframework.scheduling.support.TaskUtils$LoggingErrorHandler]
(pool-10-thread-1) Unexpected error occurred in scheduled task.:
java.lang.IllegalStateException: No transactional EntityManager
available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invokeSharedEntityManagerCreator.java:224)
[spring-orm-3.2.4.RELEASE.jar:3.2.4.RELEASE]
at com.sun.proxy.$Proxy36.unwrap(Unknown Source)
at com.jar.dom.service.SchedulerServiceImpl.recalculation(SchedulerServiceImpl.java:133)
[classes:]
at com.jar.dom.service.SchedulerServiceImpl.executeTasks(SchedulerServiceImpl.java:92)
[classes:]
I solved this adding this lines on spring configuration
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("com.blablabla")
class ApplicationConfig {
....
}

Why won't the transaction start in my junit test cases?

I have a Spring 3.1 MVC + Hibernate 3.6 project with its junit4 test suit. My problem is that there is no transaction starting in my test cases, even thought I added a #Transactional.
My test case calls a controller and a dao. In the controller, a transaction is started anyway, so it does not complain. In the dao, I added a #Transactional(propagation = Propagation.MANDATORY) to be sure it will take the test's transaction. And currently it raises an IllegalTransactionStateException, which I guess it means there is no current transaction.
I tried to create programmaticaly an transaction and it does work, which means the AOP proxy to get the dao service is not the cause of the problem. However I want to create a transaction with the #Transactional annotation.
here's my dao:
// ...imports...
#Repository("taskDao")
#Transactional(propagation = Propagation.MANDATORY)
public class TaskHome implements TaskDao {
private static final Log log = LogFactory.getLog(TaskHome.class);
private SessionFactory sessionFactory;
#Autowired
public TaskHome(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Task findById(int id) {
log.debug("getting Task instance with id: " + id);
try {
Task instance = (Task) this.sessionFactory.getCurrentSession().get(
Task.class, id); // exception raised here!
if (instance == null) {
log.debug("get successful, no instance found");
} else {
log.debug("get successful, instance found");
}
return instance;
} catch (RuntimeException re) {
log.error("get failed", re);
throw re;
}
}
...
}
Here's my test case:
// ...imports...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/test-config.xml", "/applicationContext.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class TestTaskController {
private static ClassPathXmlApplicationContext context;
private static TaskDao taskDao;
#BeforeClass
public static void initHibernate() throws Exception {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
taskDao = context.getBean("taskDao", TaskDao.class);
}
#Test
public void testOnSubmit() {
// expects an existing default transaction here
Task task = taskDao.findById(1); // fails already here
// ... calls the controller and does some tests.
}
}
I searched in all Spring's documentation and googled it in any way I could imagine, but I don't see why the transaction is not started.
Any help is very welcome.
When using #RunWith(SpringJUnit4ClassRunner.class) you should obtain beans from the application context created by SpringJUnit4ClassRunner rather than from your own one.
In your case things go wrong because #Transactional on the unit test creates a transaction in the application context managed by SpringJUnit4ClassRunner, but you call methods on the beans obtained from the application context created manually.
So, remove your #BeforeClass method and obtain TaskDao via autowiring:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/test-config.xml", "/applicationContext.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class TestTaskController {
#Autowired
private TaskDao taskDao;
...
}
See also:
9.3.5 Spring TestContext Framework

Resources