I am currently developing a REST web service using Spring, using Hibernate as the ORM and using cucumber to write acceptance tests.
To be able to rollback transactions between scenarios, I have the following code which creates a transaction before each scenario and rollback after it.
package com.orange.cainet.cucumber;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import cucumber.api.java.After;
import cucumber.api.java.Before;
@WebAppConfiguration
@ContextConfiguration("classpath:applicationContext.xml")
public class RollbackTransactionsBetweenScenarios {
@Autowired
PlatformTransactionManager transactionManager;
TransactionStatus transaction;
@Before
public void beforeScenario(){
transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
@After
public void afterScenario(){
transactionManager.rollback(transaction);
}
}
In the step definitions class, I use an autowired CrudRepository of my entity to create what is in the Given statements and assert what is in the then statements.
I use MockMvc to mock the application context and use (post, get, ...) to send fake requests and keep the ResultActions as a field to use it in assertion.
Until recently this was working good for me, the problem started when using the @Column(unique=true) to prevent duplication of one of the fields in the entity, and using DataIntegrityViolationException to know when if the entity I am about to save has a duplicated field
try{
newUser = userRespoitory.save(newUser);
}
catch(DataIntegrityViolationException exception){
throw new UserNameAlreadyExistsException();
}
the problem is that after the DataIntegrityViolationException is thrown, whenever I use UserRepository.findOne() in any cucumber step definition function, the test throws an error
org.hibernate.AssertionFailure: null id in com.komalo.domain.User entry (don't flush the Session after an exception occurs)
I understood that this is because of the transaction I am creating before each scenario, if I did a rollback of this transaction before using the UserRepository it works normally.
So my questions are:
1) is this a correct way to write tests ? , since I am using UserReposistory which is part of what I am doing the acceptance testing for, but I thought since it is implemented by Spring Data so it would be okay.
2) is there's a way to continue using a transaction even after an exception was thrown ?.
3) this can be solved easily by not depending on the DataIntegrityViolationException and instead check manually by creating an extra method in the interface like UserRepository.findOneByUsername() and use it, but isn't this an extra select statement being executed ??
Well, I do not have a clear answer, but you may better clean and reinsert the data instead, or add some logic to your code like :
if(userRepositry.findOne(newUser.getName())==null)
newUser = userRespoitory.save(newUser);
but I don't think this is a right way to use BDD.
You need to separate the Data Creation from your repositories and production code state, also your tests must be independent from each other so clean->run
looks nice strategy to me.