How to test Spring transactions - spring

I'm working to a project with Spring Boot 2.1.0 and I've the following situation.
I've the following repository
#Repository
public interface ActivityRepository extends PagingAndSortingRepository<Activity, Long> {
#Transactional
#Modifying
#Query("") // Imagine a query
void updateBacklogStatusAge();
#Transactional
#Modifying
#Query("QUERY 2") // Imagine a query
void updateNextStatusAge();
#Transactional
#Modifying
#Query("QUERY 3") // Imagine a query
void updateInProgressStatusAge();
}
and the following component
#Component
public class ColumnAgeJob {
private final ActivityRepository activityRepository;
public ColumnAgeJob(final ActivityRepository pActivityRepository) {
activityRepository = pActivityRepository;
}
#Transactional
public void update() {
activityRepository.updateBacklogStatusAge();
activityRepository.updateNextStatusAge();
activityRepository.updateInProgressStatusAge();
}
}
Now I want to test if the transactional annotation is working.
Basically my goal is to check if a runtimeException raised during the updateInProgressStatusAge() call will cause a rollback of updateNextStatusAge and updateBacklogStatusAge modifications.
How can I do that?
Thank you

You can use Mockito in order to change the behaviour of your service or repository by using #SpyBean or #MockBean.
Unfortunately #SpyBean do not works on JPA repository (https://github.com/spring-projects/spring-boot/issues/7033, this issue is for Spring boot 1.4.1, but I have the same problem with 2.0.3.RELEASE)
As workaround you can create a test configuration to create manually your mock:
#Configuration
public class SpyRepositoryConfiguration {
#Primary
#Bean
public ActivityRepository spyActivityRepository(final ActivityRepository real)
return Mockito.mock(ActivityRepository.class, AdditionalAnswers.delegatesTo(real));
}
}
And in your test:
#Autowired
private ActivityRepository activityRepository;
....
#Test
public void testTransactional() {
Mockito.doThrow(new ConstraintViolationException(Collections.emptySet())).when(activityRepository).updateInProgressStatusAge();
activityRepository.updateBacklogStatusAge();
activityRepository.updateNextStatusAge();
activityRepository.updateInProgressStatusAge();
// verify that rollback happens
}

You can change your method to test your transactional annotation.
#Transactional
public void update() {
activityRepository.updateBacklogStatusAge();
activityRepository.updateNextStatusAge();
throw Exception();
activityRepository.updateInProgressStatusAge();
}
This will simulate your desired scenario.

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

Is there a way to test nested objects without the web or persistence layer in Spring Boot?

I'm using JUnit5 to test a Spring Boot application. I want to test a #Service object, which uses #Autowired fields. I would like to mock another #Service object which is indirectly used by my test object. Concretely, I have the following (highly simplified) setup:
Object being tested:
#Service
public class MainService {
private #Autowired SubService subService;
public String test() {
return subService.test();
}
}
SubService:
#Service
public class SubService {
private #Autowired StringService stringService;
public String test() {
return stringService.test();
}
}
StringService:
#Service
public class StringService {
public String test() {
return "Real service";
}
}
Test class used:
#SpringBootTest
public class MainServiceTest {
private #Autowired MainService mainService;
private #MockBean StringService stringService;
#BeforeEach
public void mock() {
Mockito.when(stringService.test()).thenReturn("Mocked service");
}
#Test
public void test() {
assertEquals("Mocked service", mainService.test());
}
}
The above works if I run the test class as a #SpringBootTest, but this loads the full application and is very slow. I also want to avoid #WebMvcTest since I don't need the web server, or #DataJpaTest since I don't need persistence. I don't want to mock SubService, as it contains functionality I want to test together with the MainService.
I tried the following:
#ExtendWith(SpringExtension.class) => throws NoSuchBeanDefinitionException, it seems the autowiring does not work in this case
#ExtendWith(MockitoExtension.class) and using #InjectMocks and #Mock instead of the Spring annotations => as the StringService is not a direct field of the MainService being tested, this does not work.
Is there a way to use the spring dependency injection system without loading the web server or persistence layer, or alternatively not use Spring tests but allow for 'nested' dependency injection?
You can use profiling (i.e Spring #Profile) to avoid loading the whole application. It will look something like below:
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public MainService mainService() {
return new MainService();
}
#Bean
public SubService subService() {
return new SubService();
}
// mock the StringService
#Bean
public StringService stringService() {
return Mockito.mock(StringService.class);
}
}
then use that profile with `#SpringBootTest(classes = TestConfiguration.class), it will look something like below:
#ActiveProfiles("test")
#SpringBootTest(classes = TestConfiguration.class)
class MainServiceTest {
#Autowired
private MainService mainService;
#Test
public void test() {
// configure behavior using apis like when(), basically however you
// want your mock to behave
}
}
This will load only the beans defined in the class TestConfiguration.
NOTE: Since your question is more about how to avoid loading the whole application, I've answered focusing on that. The above approach will get the job done, but I'd prefer constructor wiring over any other mode of dependency injection on any given day, it's easier to maintain and test(like cases where you want to mock).

Spring 2 + JUnit 5, share #MockBean for entire test suite

I create a Spring 2.3 application using Spring Data REST, Hibernate, Mysql.
I created my tests, I've around 450 tests splitted in about 70 files. Because the persistence layer leans on a multi tenant approach (single db per tenant) using a Hikari connection pool, I've the need to avoid the pool is initializated for each test file but at the same time I need to use #MockBean because I need to mock up some repositories in the entire Spring test contest.
I create a custom annotation for all test in my suite:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public #interface TestConfig {
}
Reading many posts and the doc, I know if I use #MockBean inside a test, the Spring context is reloaded and therefore a new pool connection is created in my case.
My idea is to create a #MockBean and share it with all tests in my suite so the context is not reloaded every time.
I tried several approaches:
#Log4j2
public class TestExecutionListener extends AbstractTestExecutionListener implements Ordered {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
try {
TestDbUtils testDbUtils = (TestDbUtils) testContext.getApplicationContext().getBean(TestDbUtils.class);
testDbUtils.truncateDB();
TenantRepository tenantRepository = mock(TenantRepository.class);
testContext.setAttribute("tenantRepository", tenantRepository);
TenantContext.setCurrentTenantId("test");
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
} catch (Exception e) {
}
}
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
All my tests are annotated like this:
#TestConfig
#Log4j2
public class InvoiceTests {
#Test
public void test1(){
}
}
Unfortunately my tenantRepository.findByTenantId() is not mocked up. I also tried to create an abstract superclass:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
#MockBean
protected TenantRepository tenantRepository;
#MockBean
protected SubscriptionRepository subscriptionRepository;
#Autowired
protected TestDbUtils testDbUtils;
#BeforeAll
public void beforeAll() {
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
}
#BeforeEach
public void setup() {
testDbUtils.truncateDB();
TenantContext.setCurrentTenantId("test");
}
}
Even if my tests extended this superclass, during the run all of them were skipped (not sure why).
Is there any way to accomplish what I described?

Cannot configure #Transaction to work with Spring Data Neo4j

I'm trying to move away from manually-managed transactions to annotation based transactions in my Neo4j application.
I've prepared annotation-based Spring configuration file:
#Configuration
#EnableNeo4jRepositories("xxx.yyy.neo4jplanetspersistence.repositories")
#ComponentScan(basePackages = "xxx.yyy")
#EnableTransactionManagement
public class SpringDataConfiguration extends Neo4jConfiguration
implements TransactionManagementConfigurer{
public SpringDataConfiguration() {
super();
setBasePackage(new String[] {"xxx.yyy.neo4jplanetspojos"});
}
#Bean
public GraphDBFactory graphDBFactory(){
GraphDBFactory graphDBFactory = new GraphDBFactory();
return graphDBFactory;
}
#Bean
public GraphDatabaseService graphDatabaseService() {
return graphDBFactory().getTestGraphDB(); //new GraphDatabaseFactory().newEmbeddedDatabase inside
}
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return neo4jTransactionManager(graphDatabaseService());
}
}
I've marked my repositories with #Transactional:
#Transactional
public interface AstronomicalObjectRepo extends
GraphRepository<AstronomicalObject>{
}
I've marked my unit test classes and test methods with #Transactional and commented old code that used to manually manage transactions:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SpringDataConfiguration.class},
loader = AnnotationConfigContextLoader.class)
#Transactional
public class AstronomicalObjectRepoTest {
#Autowired
private AstronomicalObjectRepo repo;
#Autowired
private Neo4jTemplate neo4jTemplate;
(...)
#Test #Transactional
public void testSaveAndGet() {
//try (Transaction tx =
//neo4jTemplate.getGraphDatabaseService().beginTx()) {
AstronomicalObject ceres = new AstronomicalObject("Ceres",
1.8986e27, 142984000, 9.925);
repo.save(ceres); //<- BANG! Exception here
(...)
//tx.success();
//}
}
After that change the tests do not pass.
I receive:
org.springframework.dao.InvalidDataAccessApiUsageException: nested exception is org.neo4j.graphdb.NotInTransactionException
I have tried many different things (explicitly naming transaction manager in #Transactional annotation, changing mode in #EnableTransactionManagment...), nothing helped.
Will be very grateful for a clue about what I'm doing wrong.
Thanks in advance!
I found the reason...
SDN does not support newest Neo4j in the terms of transaction.
I believe it is because SpringTransactionManager in neo4j-kernel has gone in 2.2+ releases, but not 100% sure.
On github we can see that 7 hours ago the change was made to fix it:
https://github.com/spring-projects/spring-data-neo4j/blob/master/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/JtaTransactionManagerFactoryBean.java
A quick fix that worked for me was to override neo4jTransactionManager method from Neo4jConfiguration in my configuration, using Neo4jEmbeddedTransactionManager class:
#Override
public PlatformTransactionManager neo4jTransactionManager(GraphDatabaseService graphDatabaseService) {
Neo4jEmbeddedTransactionManager newTxMgr = new Neo4jEmbeddedTransactionManager(graphDatabaseService());
UserTransaction userTransaction = new UserTransactionAdapter( newTxMgr );
return new JtaTransactionManager( userTransaction, newTxMgr );
}

Mocking a service within service (JUnit)

I have the following service:
#Service
public class PlayerValidationService {
#Autowire
private EmailService emailService;
public boolean validatePlayerEmail(Player player) {
return this.emailService.validateEmail(player.getEmail());
}
Now in my junit test class i'm using a different 3rd service that uses PlayerValidationService :
public class junit {
#autowire PlayerAccountService playerAccountService ;
#Test
public test() {
this.playerAccountService .createAccount();
assertAllSortsOfThings();
}
Is it possible to mock the EmailService within the PlayerAccountService when using annotation based autowiring? (for example make the mock not checking the validation of the email via the regular email provider we work with)
thanks.
There are a couple of ways in which you could do this. First the simplest option is to ensure that your service provides a setEmailService(EmailService) method. In which case you just replace the Spring-injected implementation with your own.
#Autowired
private PlayerValidationService playerValidationService;
#Mock
private EmailService emailService;
#Before
public void setup() {
initMocks(this);
playerValidationService.setEmailService(emailService);
}
A shortcoming of that approach is that an instance of the full-blown EmailService is likely to be created by Spring. Assuming that you don't want that to happen, you can use 'profiles'.
In your test packages, create a configuration class which is only active in a particular profile:
#Configuration
#Profile("mockemail")
public class MockEmailConfig {
#Bean(name = "emailService")
public EmailService emailService() {
return new MyDummyEmailService();
}
}
And add an annotation to your test to activate that profile:
#ActiveProfiles({ "mockemail" })
public class PlayerValidationServiceTest {
//...
}

Resources