Testing of a method with 2 transactions - java

I would like to test method which have to create 2 transactions. But it seems that I can not access the session in scope of second transaction (another service method with #Transactional(propagation = Propagation.REQUIRES_NEW)). This issue happens only in tests. The code example:
#Service
public class ServiceA implements A {
#Autowired
private ServiceB serviceB;
#Transactional
public void m1() {
//some actions with repositories
serviceB.m2(id);
}
}
#Service
public class ServiceB implements B {
#Autowired
private RepositoryA repositoryA;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void m2(Long id) {
//some other actions
m3(repositoryA.findOne(id).getSomething()); // NPE is thrown here
//some other actions
}
}
I get NullPointerException but only during tests (I am sure that object with such id exists in my test databese). When I try to get the same object in scope of first transaction, everything is working as expected
public class ServiceATest extends BaseTest {
#Test
public void test() {
mockMvc.perform(get("/PATH_TO_A/" + ID).session(createSession(user)))
.andExpect(status().isOk());
}
}
I get 404 HTTP code instead of 200 on reason of NullPointerException.
How to handle such case with multiple transactions during testing via JUnit?

Related

JUnit5 test case calls #Transactional function

Given a TestClass with TestMethod for an integration test (spawning MySQL Testcontainer behind the scenes)
#SpringBootTest
public class InsertAnythingIntegrationTest {
private DoAnythingHandler handler;
#Autowired
private AnythingRepository anythingRepository;
#BeforeEach
void beforeEach() {
handler = new DoAnythingHandler(anythingRepository);
}
#Test
void handle_shouldAddEntry_givenValidValue() {
handler.insertSomething(new Entity(x,y,z));
assertThat(anythingRepository.findAll()).isEqualTo(1);
}
}
and the Handler implementation annotated with #Transactional
#Component
public class DoAnythingHandler() {
#Autowired
private AnythingRepository anythingRepository;
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Transactional
public void insertOrUpdateSomething(Entity entity) {
var existingEntity = anythingRepository.findById(entity.getId());
if (existingEntity != null) {
existingEntity.valueX = entity.x;
existingEntity.valueY = entity.Y;
existingEntity.valueZ = entity.Z;
} else {
anythingRepository.save(entity);
}
applicationEventPublisher.publishEvent(new AnyFurtherEventUpdatingDb(entity.X, entity.Y));
}
}
If I run this test, the transaction is never opened since it needs to be called from Bean to Bean to apply the #Transactional annotation.
How can I mock a Bean calling this method in my test case.
I don't want my test case to be #Transactional since I am not able to assert anything. This handler is my UnitOfWork and I want no further abstraction layer whatever in place.
How to approach this?
#Autowired
private DoAnythingHandler handler;
did the trick for me.
I thought I tried this before, but maybe I did something wrong before.
Thanks to everyone!

How to write spring boot unit test for a method which calls another #Async method inside it?

I need to write integration test for "processEvent" method which calls a #Async method inside it. I tried writing a test for this method, but the issue was it did not save the Student object to the DB. However removing #Async annotation allows me to save the object. I want to know how should I write Test cases for this method, eliminating the #Async issue. I want to save the student object while testing. I have attached my code and below.
Here is the ClassA and it has the method I want to test.
#Service
public class ClassA {
private final ConfigurationProcessor<Student> configurationProcessor;
#Autowired
public ClassA(ConfigurationProcessor<Student> configurationProcessor) {
this.configurationProcessor = configurationProcessor;
}
public void processEvent(Configuration configuration) {
configurationProcessor.process(configuration);
}
}
This is the interface ConfigurationProcessor class
public interface ConfigurationProcessor<T> {
void process(Configuration configuration);
}
And this is its Impl class
#Service
public class ConfigurationProcessoeStudents10Impl implements ConfigurationProcessor<Student> {
private final StudentRepository studentRepository;
#Autowired
public ConfigurationProcessoeStudents10Impl(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
#Override
#Async
public void process(Configuration configuration) {
studentRepository.save(Student.builder().Name(configuration.name).Age(configuration.age));
}
}
This is the Test I have written so far.
#EnableAutoConfiguration
#SpringBootTest
public class AudienceC10IT {
#Autowired
ClassA classA;
#Test
#Tag("VerifyProcess")
#DisplayName("Verify kafka event consumer from configuration manager")
void verifyProcess(){
Configuration configuration = new Configuration("lal",12);
classA.processEvent(configuration);
}
}
If you have have set up a ThreadPoolTaskExecutor bean you can Autowire it in your test.
Then, after you call your async method, you can await the termination.
Then you can check for the expected behaviour / result.
Something like this:
#Autowired
private ThreadPoolTaskExecutor asyncTaskExecutor;
#Test
void test() {
callAsyncMethod();
boolean terminated = asyncTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
assertAsyncBehaviour();
}

Transactional annotation not working as expected

I have an issue with rollbacking in #Transactional. When rollback is called, rollback itself is working, but another method after is executed.
#Transactional(rollbackFor = { Exception.class })
#Service
public class Service {
public void method1() {
stuffToGetListOfObjects();
deleteAllAndSaveAll(listOfObejcts);
Util.staticMethod();
}
private deleteAllAndSaveAll(List list) {
repository.deleteAll();
repository.saveAll(list);
}
}
#SpringBootApplication
public class Application implements ApplicationRunner {
private final Service service;
#Autowired
public Application(Service service) {
this.service = service;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
service.method1();
}
}
When something goes wrong during the insertion in repository.saveAll(list), no data is actually deleted which is fine and expected. The issue is program is going on the Util.staticMethod() method is executed "after" rollback.
I know that static methods cannot be #Transactional and private methods are ignored, but that doesn't seem the issue here. According to the log, everything in method1() is executed first and after that, the inserting is happening. I guess I need to pick out the Util.staticMethod() calling from transaction somehow.
That's why you should separate the repository actions from the service functions. Repository actions are rollbacked. If you separate them the repository level will throw an error and rollback itself. And before the static method, you can check whether the transaction is finished or not by #transactionaleventlistener.

My service test fails when I have an if statement in the class under test SpringBoot

I am writing tests from my springboot application. The class has a method getUserById which returns Optional<User>. This methos has an if statement that will check whether an row was returned from repository before sending a response.
Problem:
With the if statement in place, my test always throws the error in the if statement. when I remove the if statement, the test passes. What am I missing?
This is my UserServiceImpl (Class under test)
#Service
#RequiredArgsConstructor
#Transactional
#Slf4j
public class UserServiceImpl implements UserService, UserDetailsService {
#Autowired
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Override
public List<User> getUsers() {
log.info("Fetching users");
return userRepository.findAll();
}
#Override
public Optional<User> getUserById(Long id) {
log.info("Fetching user id: {}", id);
Optional<User> user = userRepository.findById(id);
if (!user.isPresent()) {
throw new ResourceNotFoundException(MessageUtil.ERROR_USER_NOTFOUND);
}
return user;
}
}
This is my UserServiceImplTest (test class)
#RunWith(SpringRunner.class)
#SpringBootTest
class UserServiceImplTest {
#MockBean
private UserRepository userRepositoryTest;
#InjectMocks
private UserServiceImpl userServiceTest;
#Mock
private PasswordEncoder passwordEncoder;
private List<User> userSet;
private User user1;
private User user2;
#BeforeEach
void setUp() {
userServiceTest = new UserServiceImpl(userRepositoryTest, passwordEncoder);
Set<ApplicationUserRole> roles = new HashSet<>();
roles.add(ApplicationUserRole.TEST_USER);
userSet = new ArrayList<>();
user1 = User.builder().nickname("test-nickname")
.id(1L)
.username("254701234567")
.roles(roles)
.password("password")
.build();
user2 = User.builder().nickname("test2-nickname2")
.id(2L)
.username("254701234589")
.roles(roles)
.password("password")
.build();
userSet.add(user1);
userSet.add(user2);
userSet.stream().forEach(user -> {
userServiceTest.saveUser(user);
});
}
#AfterEach
void tearDown() {
}
#Test
void testGetUsers() {
when(userServiceTest.getUsers()).thenReturn(userSet);
assertEquals(2, userServiceTest.getUsers().size());
verify(userRepositoryTest).findAll();
}
#Test
void testGetUserById() {
when(userServiceTest.getUserById(user1.getId())).thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
#Test
void testSaveUser() {
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals(1L, user1.getId());
verify(userRepositoryTest).save(user1);
}
#Test
void updateUser() {
user1.setNickname("nickname-update");
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals("nickname-update", user1.getNickname());
verify(userRepositoryTest).save(user1);
}
}
NOTE: Other tests work just fine
None of your tests set up the repository mock. You are trying to mock the service method instead, which will implicitly call the real method while mocking. But the service method is never called to assert correct behavior. In other words: your service's behavior is never exercised by the test, because the return value of the method calls is overwritten.
Example:
#Test
void testGetUsers() {
// repository is never mocked
// vvvvvvvvvvvvvvvvvvvvvvvvvv--- this calls the service method
when(userServiceTest.getUsers()).thenReturn(userSet);
// ^^^^^^^^^^^^^^^^^^^--- this overwrites the return value of the service method
assertEquals(2, userServiceTest.getUsers().size()); // this uses the overwritten return value
verify(userRepositoryTest).findAll();
}
To fix, you need to mock the repository (not the service) and then call the real service. It is also quite useless to assert the user's id, because the user is set up by the test, not in the classes under test.
#Test
void testGetUserById() {
// arrange
when(userRepositoryTest.getUserById(user1.getId())
.thenReturn(Optional.ofNullable(user1));
// act
Optional<User> userById = userServiceTest.getUserById(user1.getId());
// assert
assertEquals(1, user1.orElseThrow().getId());
verify(userRepositoryTest).findById(user1.getId());
}
I'd also question your usage of verify at the end of test. You are testing implementation details here. You should only be interested in the return value of your service, not which methods of the repository it is calling. Especially since you are mocking those methods anyway, so you already know with which arguments they are called; otherwise the mock would not return the configured return value in the first place.
A side-question is why your if-condition is always true and the exception always thrown. Again: incorrect/missing setup of your mocks.
#Test
void testGetUserById() {
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv--- calls real method, but the repository mock is not set up
when(userServiceTest.getUserById(user1.getId()))
.thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
You are trying to mock the service method call but the service is autowired — that doesn’t work. You can mock instead the repository method call since the repository is annotated as MockBean, that should work.

Mocking Spring bean's method behavior breaks aspects

I searched SO and found bunch of other questions that looked similar but not exactly, so I'll ask another one.
I have Spring application and say I created custom aspect (looking for CatchMe annotation) to log exceptions in a specific way. I want to test the aspect by mocking the behavior of one of my Spring #Service class's method so it throws exception when it is called. Then in another method, annotated with my custom annotation #CatchMe, I call the first method. What I expect to happen is the exception to get logged. Unfortunatelly the exception is thrown but the aspect is not triggered. So how can I make the aspect to get triggered in this test using Mockito?
Note: I've checked those (plus a bunch more):
Unit testing Spring #Around AOP methods
Spring Aspect not triggered in unit test
Spring: cannot inject a mock into class annotated with the #Aspect annotation
but most of them are Controller related and not Service related and I want to test only the service.
The Test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {BeanConfig.class})
public class MyServiceTest {
#Autowired
#InjectMocks
private MyService service;
#Mock
private MyServiceDependency serviceDep;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(service, "serviceDep", serviceDep);
}
#Test
public void test() {
when(serviceDep.process()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
throw new Exception("Sample message.");
}
});
service.execute();
}
}
Services
#Service
public class MyService {
#Autowired
private MyServiceDependency serviceDep;
#CatchMe
public void execute() {
serviceDep.process();
}
}
#Service
public class MyServiceDependency {
public Object process() {
// may throw exception here
}
}
Configuration and Aspect
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.example.services"})
public class BeanConfig { .. }
#Aspect
#Component
public class CatchMeAspect {
#Around("#annotation(CatchMe)")
public Object catchMe(final ProceedingJoinPoint pjp) throws Throwable {
try {
pjp.proceed();
} catch (Throwable t) {
// fency log
}
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CatchMe {}
EDIT: The functionality works but I want to verify it with the test.
Actually it is working as expected, however you are running in a side effect of proxy based AOP, especially class based proxies in this case.
Currently you are setting the field on the proxy and not on the actual object inside the proxy. Which is what you actually want. To obtain the actual instance use AopTestUtils.getUltimateTargetObject and then use that in the ReflectionTestUtils.setField method.
#Autowired
#InjectMocks
private MyService service;
#Mock
private MyServiceDependency serviceDep;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
MyService serviceToInject = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setField(serviceToInject, "serviceDep", serviceDep);
}
However I think that approach is wrong, when you start messing around like this there is a better way. Simply use Spring to inject the mock. Create a specific #Configuration class for this test case. Make it a internal public static class and for the dependency add a mocked #Bean.
#Configuration
#Import(BeanConfig.class)
public static class TestBeanConfig {
#Bean
public MyServiceDependency myServiceDependency() {
return Mockito.mock(MyServiceDependency.class);
}
}
Now in your test class you can simply #Autowire both beans and not need to use reflection or whatever to set dependencies.
#RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyServiceDependency serviceDep;
#Test
public void test() {
when(serviceDep.process()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
throw new Exception("Sample message.");
}
});
service.execute();
}
}
Which will take care of the correct dependencies.
I had the same problem as #nyxz and this is intentional, see https://github.com/spring-projects/spring-boot/issues/7243.
Inspired by #M. Deinum following solution worked for me with Spring Boot 2.3.4.RELEASE and JUnit 5.
We will just provide a mocked bean without #MockedBean
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MyServiceTest {
#Autowired
private MyService service;
#Test
public void test() {
service.execute();
}
static class TestBeanConfig {
#Bean
#Primary
public MyServiceDependency myServiceDependency() {
MyServiceDependency myServiceDependency = Mockito.mock(MyServiceDependency.class)
// Add behavior of mocked bean here
return myServiceDependency;
}
}
}

Categories