I want to test a transactional method. I added the #transactional over test method but I got an exception IllegalStateException: Failed to retrieve PlatformTransactionManager for #Transactional test. I don't know how to activate the transaction. I tried to add TestTransaction.start() in the first line of the test body but it throws an exception that the transaction is not active. I don't know what's wrong with my test.
If I remove classes in #SpringbootTest the test randomly throws NoSuchBeanException for my repositories.
My test method:
#ExtendWith(SpringExtension.class)
#DataJpaTest
#AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class PersistenceHelperTest {
#Autowired
private ReturnRepository returnRepository;
#SpyBean
private PersistenceHelper persistenceHelper;
#Test
void removeFromDbOnWriteExceptionForNotWritableFile(#TempDir Path tempDir) {
Path testFile = tempDir.resolve("testFile.txt");
try {
String content = "This is a test";
testFile.toFile().createNewFile();
boolean writable = testFile.toFile().setWritable(false);
assertTrue("File should not be writable", writable);
persistenceHelper.saveToFileAndDB(content.getBytes(), testFile.toFile().getAbsolutePath(), returnFile, returnRepository);
fail();
} catch (Exception e) {
assertTrue("Should be instance of IOException", e instanceof IOException);
assertTrue("Should exists", Files.exists(testFile));
assertSame(0, returnRepository.findAll().size());
}
}
The class under the test:
#Component
public class PersistenceHelper {
#Transactional(rollbackFor = {IOException.class}, propagation = Propagation.REQUIRES_NEW)
public <T extends BaseEntity> void saveToFileAndDB(byte[] fileContent, String fileAbsolutePath, T entity, JpaRepository<T, Long> jpaRepository) throws IOException {
FileTransactionListener transactionListener = new FileTransactionListener(new FileDeleter(), fileAbsolutePath);
TransactionSynchronizationManager.registerSynchronization(transactionListener);
jpaRepository.save(entity);
FileUtil.writeToFile(fileAbsolutePath, fileContent);
}
}
my Springboot class:
#SpringBootApplication
#EnableTransactionManagement
#EnableJpaRepositories("packageAddress")
public class MyApplication {
Related
I have an application that listens for Kafka messages using #KafkaListener inside of a #Component. Now I'd like to make an integration test with a Kafka test container (which spins up Kafka in the background). In my test I want to verify that the listener method was called and finished, however when I use #SpyBean in my test I get:
No bean found for definition [SpyDefinition#7a939c9e name = '', typeToSpy = com.demo.kafka.MessageListener, reset = AFTER]
I'm using Kotling, important classes:
Class to test
#Component
class MessageListener(private val someRepository: SomeRepository){
#KafkaListener
fun listen(records: List<ConsumerRecord<String, String>>) {
// do something with someRepository
}
}
Base test class
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KafkaContainerTests {
// some functionality to spin up kafka testcontainer
}
Test class
class MessageListenerTest #Autowired constructor(
private val someRepository: SomeRepository
) : KafkaContainerTests() {
#SpyBean
private lateinit var messageListenerSpy: MessageListener
private var messageListenerLatch = CountDownLatch(1)
#BeforeAll
fun setupLatch() {
logger.debug("setting up latch")
doAnswer {
it.callRealMethod()
messageListenerLatch.count
}.whenever(messageListenerSpy).listen(any())
}
#Test
fun testListener(){
sendKafkaMessage(someValidKafkaMessage)
// assert that the listen method is being called & finished
assertTrue(messageListenerLatch.await(20, TimeUnit.SECONDS))
// and assert someRepository is called
}
}
The reason I am confused is that when I add the MessageListener to the #Autowired constructor of the MessageListenerTest it does get injected successfully.
Why is the test unable to find the bean when using #SpyBean?
It works fine for me with Java:
#SpringBootTest
class So58184716ApplicationTests {
#SpyBean
private Listener listener;
#Test
void test(#Autowired KafkaTemplate<String, String> template) throws InterruptedException {
template.send("so58184716", "foo");
CountDownLatch latch = new CountDownLatch(1);
willAnswer(inv -> {
inv.callRealMethod();
latch.countDown();
return null;
}).given(this.listener).listen("foo");
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
verify(this.listener).listen("foo");
}
}
#SpringBootApplication
public class So58184716Application {
public static void main(String[] args) {
SpringApplication.run(So58184716Application.class, args);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so58184716").partitions(1).replicas(1).build();
}
}
#Component
class Listener {
#KafkaListener(id = "so58184716", topics = "so58184716")
public void listen(String in) {
System.out.println(in);
}
}
I have this test class:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = { CrimeServiceDBImpl.class, CrimeController.class, Crime.class })
#ComponentScan("com.springmiddleware")
#EntityScan(basePackages = {"com.springmiddleware.entities"})
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
#EnableJpaRepositories("com.springmiddleware")
public class TestCrimeServiceDB {
#Autowired
private CrimeServiceDBImpl service = new CrimeServiceDBImpl();
#Test
public void getAll() {
try {
List<Crime> list = this.service.getAllCrimes();
assertTrue(list.size()!=0);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The method getAllCrimes() from the service class does just this:
#Service
public class CrimeServiceDBImpl implements CrimeService{
#Autowired
private CrimeRepository repository;
private List<Crime> list = new ArrayList<Crime>();
public CrimeServiceDBImpl() {
list = UtilityMethods.readFromCSV();
};
#Override
public List<Crime> getAllCrimes() throws IOException {
repository.saveAll(list);
return this.repository.findAll();
}
If I call this method when running the application, it correctly add all my objects to the database, but when it's called from the test it doesn't add anything, but no exception is thrown.
Which database are you using? Do you mean the data is not persisted in the database after the test has finished? That's because a test always perform a rollback/cleanup when it has finished its work.
I want to seperate test data for particular test. For example we have two test classes A and B they are running on diffrent data so I want to create
ATestData.xml and BTestData.xml and when test class starts it should execute ATestData.xml then rollback then start test B execute BTestData.xml.
in Arquillian Persistence Extension there was #UsingDataSet("datasets/users.yml").
I started to wrtie my own annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface LiquibaseTestDataFile {
String value() default "";
}
And
#Slf4j
#Aspect
#Component
public class LiquibaseTestDataFileAspect {
#Before("execution(* (#LiquibaseTestDataFile *).*(..)) || execution(#LiquibaseTestDataFile * *(..))")
public void insertTestData(JoinPoint joinPoint){
log.info("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class<?> declaringClass = signature.getMethod().getDeclaringClass();
LiquibaseTestDataFile myAnnotation = declaringClass.getAnnotation(LiquibaseTestDataFile.class);
//TODO if null maybe on method
//log.info(myAnnotation.value());
}
}
But it not trigers when annotation is on test class.
I am using #EnableAspectJAutoProxy in configuration
and
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TransferManagerRestApplication.class})
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
in test class.
I have find solution by using #PostConstruct
#Component
public class LiquibaseDataTestConfiguration {
#Autowired
private SpringLiquibase springLiquibase;
public void insert(String fileName){
try {
Connection connection = springLiquibase.getDataSource().getConnection();
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
Liquibase liquibase = new liquibase.Liquibase(fileName, new ClassLoaderResourceAccessor(), database);
liquibase.update(new Contexts(springLiquibase.getContexts()), new LabelExpression(springLiquibase.getLabels()));
} catch (LiquibaseException | SQLException e) {
e.printStackTrace();
}
}
}
and in test class
#Autowired
private LiquibaseDataTestConfiguration liquibaseDataTestConfiguration;
#PostConstruct
public void initData(){
liquibaseDataTestConfiguration.insert("liquibase/AClassTest.xml");
}
I need to integrate the cucumber with spring in a practice. But I can't import some test data with spring's #Sql annotation. However the other integration tests that not use cucumber works fine. I didn't find why? The cucumber runner:
#RunWith(Cucumber.class)
#CucumberOptions(features={"classpath:features/"})
public class CucumberIT {
}
And the step defination:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes={DevDbConfig.class})
#Sql("classpath:test-reader-data.sql")
#Transactional
#ActiveProfiles("dev")
public class StepDefines {
#Autowired
ReaderService readerService;
Reader reader;
#Given("^a user with name Lily$")
public void a_user_with_name_Lily() throws Throwable {
reader = readerService.findByName("Lily"); // reader is null here!
}
#Given("^this user Lily exists$")
public void this_user_Lily_exists() throws Throwable {
assertThat(reader, notNullValue());
}
#Then("^Lily's info should be returned$")
public void lily_s_info_should_be_returned() throws Throwable {
assertThat(reader.getName(), is("Lily"));
}
}
But the following testing code can import the testing data normally:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes={DevDbConfig.class})
#Sql("classpath:test-reader-data.sql")
#Transactional
#ActiveProfiles("dev")
public class ReaderServiceTest {
#Autowired
ReaderService readerService;
#Autowired
EntityManager entityManager;
#Test
public void testQueryByIdAndShouldNotReturnNull() {
Reader reader = readerService.findByName("Lily");
assertThat(reader, notNullValue()); // reader is not null!
}
}
Thanks!
The #Sql will be executed by SqlScriptsTestExecutionListener. The Spring SpringJUnit4ClassRunner will register all of the TestExecutionListeners.
From theSqlScriptsTestExecutionListenerjava doc, it says:
Scripts and inlined statements will be executed before or after execution of the corresponding test method, depending on the configured value of the executionPhase flag.
So a class level #Sql is equivalent to all #Testmethods with individual #Sqlannotation.
However, when cucumber is running, it won't execute any #Test method and the #Sql doesn't have chance to be executed.
Finally, I have to do it myself:
#Autowired
DataSource ds;
#Given("^a user with name Lily$")
public void a_user_with_name_Lily() throws Throwable {
ScriptUtils.executeSqlScript(ds.getConnection(), new ClassPathResource("test-reader-data.sql"));
reader = readerService.findByName("Lily");
}
#Given("^this user Lily exists$")
public void this_user_Lily_exists() throws Throwable {
assertThat(reader, notNullValue());
}
#Then("^Lily's info should be returned$")
public void lily_s_info_should_be_returned() throws Throwable {
assertThat(reader.getName(), is("Lily"));
}
I'm trying to execute sample of transaction code. I throw checked exception from transactional method and set rollbackFor parameter for the class of this exception. However this transaction doesn't rollback. So my question is what may be the reason of that behaviour. Here is a sample of my code:
MyService:
#Service
#Transactional
#EnableTransactionManagement
public class MyService implements IMyService {
#Autowired
private IMyDao myDao;
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = BreakException.class)
public void test() throws BreakException {
myDao.updateMethodA("64", "'N'", "'2015.01.01/0008661/0008'");
if(true) throw new BreakException();
myDao.updateMethodB("15", "'N'", "'2015.01.01/0008661/0008'");
}
}
MyDao:
#Repository
public class MyDao implements IMyDao {
#Override
#Transactional(propagation=Propagation.MANDATORY)
public void updateOperationA(String x, String y, String z) {
String sql = "UPDATE ...."; //masked for example
jdbcTemplate.update(sql);
}
#Override
#Transactional(propagation=Propagation.MANDATORY, rollbackFor = Exception.class)
public void updateOperationB(String saldoPo, String saldo_status, String unikalne_id) throws BreakException {
String sql = "UPDATE ...."; //masked for example
//if(true) throw new BreakException();
jdbcTemplate.update(sql);
}
}
1.) So when I call test() on MyService the update from method updateMethodA is not rolledback after BreakException() is thrown.
2.) BreakException is my class that extends Exception
3.) When I throw BreakException from updateOperationB(commented in example) it does rollback correctly.
4.) When I change BreakException to extend TransactionException it works as expected so it seems the problem is only when I extend Exception.