Unit test exception with database without breaking other test - java

I have a problem when I tried to test my class with the database. I am not using hibernate so I cannot rollback database. When exception occurs the database operation stops the test for the exception passes but it fails other test I wrote. How can I prevent this?
#RunWith(SpringJunit4ClassRunne.class)
#ContextConfiguration("test.xml")
public class MachineDaoTest{
#Autowired
private DataSource ds;
#Before
public void setUp(){...}
#Test
public void testFindVersion(){
....
}
#Test
public void testException() {
try {
someMethod.getVersion("xxyyzz");
fail("Expected DB Exception");
} catch (Exception e) {
assertTrue(e instanceof DbException);
}
Now in xml file I have included two sql file. One create tables and set values and another one that drop tables and cause exception. Since the database was autowired how am I able to rollback or make both test pass?
In an actual java file to be tested ...
...
try{
result = someMethod.getVersion("x");
if(result==null) {
throw new DBexception("result should return");
} catch (DBexception e) {
e.getMessage();
}
...

Related

How do I get Java Spring Boot #transactional to work with rollbacks?

I'm trying to use the #transactional feature from Spring Boot for rollbacks.
I have a controller that calls a method which has a few sub methods it calls before inserting records into db. I'm putting the **#transactional** declaration above a parent method like the sudo codebelow.
My expectation is that the db inserts from processAB() method (sudo code below) would be reverted upon exception.
//controller
public StuffEntity<List<string>> processLogic(param1, param2){
parentMethod(param1,param2);
}
#Transactional
private void parentMethod(Date processD, String processS){
// some business logic code
processE(processD, processS);
}
private void processE(Date processingD, String processingS){
// additional business logic
try{
if(noProcessErrors){
handleSuccess(a, b);
}else{
// handles error
handleProcessErrors(a,b);
}catch (Exception e){
Log(e);}
}
public void handleSuccess(a,b) throws CustomException {
// success logic
processAB();
}
public void processAB() throws CustomException {
// update db
throw exception; // for test
}

Continue with transaction after exception - JPA

I am using JPA with Spring. I am trying to do batch import. If there is problem with batch import then I would like to insert individually, and if this fails also then I would like to save to duplicates table. I wrote a logic for this but I get this error everytime:
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
Mine setting for JPA are like this:
#Bean(name = "dataSource", destroyMethod = "")
public DataSource getDataSource() {
return new JndiDataSourceLookup().getDataSource(props.getDbJndiName());
}
#Bean
public JpaVendorAdapter getHibernateJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean getEntityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean lcemfb = new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(getDataSource());
lcemfb.setPersistenceUnitName("MyPU");
lcemfb.setPackagesToScan("com.project");
lcemfb.setJpaVendorAdapter(getHibernateJpaVendorAdapter());
lcemfb.setJpaProperties(getHibernateProperties());
return lcemfb;
}
#Bean
public Properties getHibernateProperties() {
Properties jpaProperties = new Properties();
jpaProperties.put(DIALECT, "org.hibernate.dialect.Oracle10gDialect");
jpaProperties.put(SHOW_SQL, true);
jpaProperties.put(AUTOCOMMIT, true);
jpaProperties.put(FORMAT_SQL, true);
jpaProperties.put(USE_SQL_COMMENTS, true);
jpaProperties.put(STATEMENT_BATCH_SIZE, 20);
jpaProperties.put(ORDER_INSERTS, true);
jpaProperties.put("hibernate.ejb.entitymanager_factory_name", "MyEM");
return jpaProperties;
}
#Bean
public JpaTransactionManager getTransactionManager() {
return new JpaTransactionManager(getEntityManagerFactoryBean().getObject());
}
#Bean
public PersistenceExceptionTranslationPostProcessor getPersistenceExceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
I get entity manager like this
#PersistenceContext(unitName = "MyPU")
private EntityManager em;
protected EntityManager em() {
return em;
}
my import method is:
#Override
#Transactional
public void importBusinessFile(MultipartFile file)
throws GeneralException, IOException {
// process file
//save batch
dealsRepository.saveBatch(deals);
}
and saveBatch method from repository:
public void saveBatch(List<Deal> list) {
for (Deal deal : list) {
em().persist(deal);
}
try {
em().flush();
em().clear();
} catch (Exception e) {
log.info("Duplicates detected, save individually.", e);
for (Deal deal : list) {
try {
save(deal);
} catch (Exception ex) {
log.error("Problem saving individual deal", e);
// TODO write to duplicates
}
}
}
}
I tried setting dontRollbackOn but I can't get past this exception. I found some other similar threads but none helped me.
In case if you method has #Transactional annotation, occurrence of any exception inside your method marks the surrounding transaction as roll-back.
You can add an attribute for #Transactional annotation to prevent it of rolling back like : #Transactional(noRollbackFor=Exception.class). Spring rollback transaction for all sub type of runtime exceptions.
If you want to do something when you catch you should try to do it in new transaction.But remeber that self invocation in spring not supported , you can't just call transactional method2 from method1 , you should get from spring context current service and call method2.
PROPAGATION_NESTED uses a single physical transaction with multiple
savepoints that it can roll back to. Such partial rollbacks allow an
inner transaction scope to trigger a rollback for its scope, with the
outer transaction being able to continue the physical transaction
despite some operations having been rolled back. This setting is
typically mapped onto JDBC savepoints, so will only work with JDBC
resource transactions. See Spring’s DataSourceTransactionManager.
simple variant :
#Autowired
private ApplicationContext context.
#Override
#Transactional
public void importBusinessFile(MultipartFile file)
throws GeneralException, IOException {
// process file
try{
dealsRepository.saveBatch(deals);
//in case fail-transaction for saveBatch is rollback main transactio is active
}catch(Exception e){
context.getBean(curent serivce).tryReSaveBatch(deals);
//in case fail - transaction for tryReSaveBatchis rollback ,
main transactio is active
}
// main transaction commited
}
#Transactional(propagation = NESTED)
public void saveBatch(List<Deal> list) {
for (Deal deal : list) {
em().persist(deal);
}
}
#Transactional(propagation = NESTED)
public void tryReSaveBatch(List<Deal> list) {
for (Deal deal : list) {
try {
save(deal);
} catch (Exception ex) {
log.error("Problem saving individual deal", e);
// TODO write to duplicates
}
}
}
I only managed to fix this by creating another bean containing batch import method. So after that Spring can intercept the call from this bean and start a new transaction.

Unit test for thrown exception

I have a Spring Boot application where I have methods in my Service layer like:
public List<PlacementDTO> getPlacementById(final int id) throws MctException {
List<PlacementDTO> placementList;
try {
placementList = placementDao.getPlacementById(id);
} catch (SQLException ex) {
throw new MctException("Error retrieving placement data", ex);
}
return placementList;
}
What is the best way to unit test that MctException will be thrown? I tried:
#Test(expected = MctException.class)
public void testGetPlacementByIdFail() throws MctException, SQLException {
when(placementDao.getPlacementById(15)).thenThrow(MctException.class);
placementService.getPlacementById(15);
}
However, this doesn't test the right that an exception is actually thrown.
I think you have to stub the placementDao.getPlacementById(15) call to throw the SQLException instead of your MctException, like this:
#Test(expected = MctException.class)
public void testGetPlacementByIdFail() throws MctException, SQLException {
when(placementDao.getPlacementById(15)).thenThrow(SQLException.class);
placementService.getPlacementById(15);
}
This way when you call your Service method placementService.getPlacementById(15); you know that your MctException will encapsulate the SQLException and therefore your test could expect the MctException exception to be thrown.
You may want to try out the ExepctionException rule feature of Junit. This would allow greater granularity in verification of your exception handling in your unit test than the expected exception annotation.
#Rule
public ExpectedException thrown= ExpectedException.none();
#Test
public void testGetPlacementByIdFail(){
thrown.expect(MctException.class);
thrown.expectMessage("Error retrieving placement data");
//Test code that throws the exception
}
As the above snippet show, you would also be able to test on various properties of the exception like its message.

Nested #Transactional and exceptions

i want to perform some operations and store it's result (success or failure) in db.
#Service
public MyService {
#Transactional
public void myOperation() {
try {
otherService.doDatabaseOperationThatMayFail();
repository.writeSuccess()
} catch (Exception e) {
repository.writeFailure()
}
}
}
But if otherService.doDatabaseOperationThatMayFail() itself is marked as #Transactionalor uses such components and any of those methods throws an exception then the whole transaction is marked for rollback. I can't remove #Transactional from all services used by MyService. So how can I store the failure info in my database?

Exception handling in CMT stateless bean

Is it possible to catch an exception in a CMT(Container Managed Transaction) stateless bean?
The code below wont catch any exeption when I tried it. If I use BMT(Bean Managed Transaction), I can catch the exception. But I want to remain with CMT.
#Path("books")
public class BookResource
{
#EJB
private BooksFacade book_facade;
private Books local_book;
#POST
#Consumes({"application/xml", "application/json"})
public Response create(Books entity)
{
try
{
book_facade.create(entity);
} catch (RuntimeException ex)
{
System.out.println("Caught database exception");
}
return Response.status(Response.Status.CREATED).build();
}
public class TXCatcher
{
//#Resource
//UserTransaction tx;
private final static Logger LOG = Logger.getLogger(TXCatcher.class.getName());
#AroundInvoke
public Object beginAndCommit(InvocationContext ic) throws Exception
{
//ic.proceed();
System.out.println("Invoking method: " + ic.getMethod());
try
{
//tx.begin();
Object retVal = ic.proceed();
//tx.commit();
return retVal;
}catch (RollbackException e)
{
LOG.log(Level.SEVERE, "-----------------Caught roolback(in interceptor): {0}", e.getCause());
System.out.println("Invoking method: " + ic.getMethod());
throw new CustomEx("Database error");
}catch (RuntimeException e)
{
LOG.log(Level.SEVERE, "-----------------Caught runtime (in interceptor): {0}", e.getCause());
System.out.println("Invoking method: " + ic.getMethod());
//tx.rollback();
throw new CustomEx("Database error",e.getCause());
//throw new CustomEx("Database error");
}
//return ic.proceed();
}
}
It depends what kind of problem you're trying to catch. You could try an explicit EntiyManager.flush, but depending on your data source isolation level, some errors cannot be caught until transaction commit, and there is no mechanism for catching transaction commit errors for a CMT. If that's the case, your only option is to use BMT (even though you said you don't want to). The only suggestion that might make that more palatable would be to write an EJB interceptor that behaves similarly to CMT (that is, inject UserTransaction into the interceptor, and begin/commit/rollback in the #AroundInvoke).
By placing the following above my function in my BooksFacade class create function, the CMT created a 2nd transaction within the first transaction. When the exception was thrown from the 2nd transaction, my BookResource class create method could catch it. No need for BMT.
#Overide
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void create(Books entity)
{
super.create(entity);
}
I noted that the annotation only works when placed on the individual methods, by placing it on the class itself wont make a difference.

Categories