Why is a bean created twice in test when using #PostConstruct? - spring-boot

I have a configuration class that uses a properties file and it works properly.
Now I want to test that code and I have to recognize that the method annotated with #PostConstruct is run twice during the test. (In debug mode I can see that the for-loop is conducted twice.)
The configuration class:
#Slf4j
#RequiredArgsConstructor
#Configuration
#ConfigurationPropertiesScan("com.foo.bar")
public class MyConfig {
private final MyProperties myProperties;
#Autowired
private GenericApplicationContext applicationContext;
#PostConstruct
void init() {
Objects.requireNonNull(myProperties, "myProperties may not be null");
for (final MyProperties.MyNestedProperty nested : myProperties.getApps()) {
log.info("xxx {} created.", nested.getName());
applicationContext.registerBean(nested.getName(), MyContributor.class, nested);
}
}
}
The used properties class:
#Slf4j
#Data
#Validated
#ConfigurationProperties(prefix = MyProperties.CONFIG_PREFIX)
public class MyProperties {
public static final String CONFIG_PREFIX = "xxx";
#Valid
#NestedConfigurationProperty
private List<MyNestedProperty> apps;
#Data
public static class MyNestedProperty {
#NotNull
#NotEmpty
private String abc;
private String xyzzy;
#NotNull
#NotEmpty
private String name;
}
}
My attempt with the test class:
#ExtendWith(SpringExtension.class)
#RequiredArgsConstructor
#ContextConfiguration(classes = MyConfigTest.MyTestConfiguration.class)
class MyConfigTest {
#MockBean
MyProperties myProperties;
ApplicationContextRunner context;
#BeforeEach
void init() {
context = new ApplicationContextRunner()
.withBean(MyProperties.class)
.withUserConfiguration(MyConfig.class)
;
}
#Test
void should_check_presence_of_myConfig() {
context.run(it -> {
assertThat(it).hasSingleBean(MyConfig.class);
});
}
// #Configuration
#SpringBootConfiguration
// #TestConfiguration
static class MyTestConfiguration {
#Bean
MyProperties myProperties() {
MyProperties myProperties = new MyProperties();
MyProperties.MyNestedProperty nested = new MyProperties.MyNestedProperty();
nested.setName("xxx");
nested.setAbc("abc");
nested.setXyz("xyz");
myProperties.setApps(List.of(nested));
return myProperties;
}
}
}
Why does this happen and how can I prevent this behaviour?

Related

MapStruct mapper not initialized with autowired when debug

I use spring boot 2.3.2 with mapstruct.
In a service class I have a mapper who have an autowired annotation.
#Service
public BillingService{
private BillingRepository billingRepository;
#Autowired
private BillingMapper billingMapper;
#Autowired
public BillingService (BillingRepository billingRepository){
this.billingRepository=billingRepository;
}
public void getBilling(Long billingId){
....
billingMapper.convertTo(...);
}
}
#RunWith(MockitoJUnitRunner.class)
public class BillingServiceTest{
#Mock
BillingRepository billingRepository;
private BillingService bilingService;
#Spy
private BillingMapper billingMapper = Mappers.getMapper(BillingMapper.class);
#BeforeEach
public void setup(){
MockitoAnnotations.initMocks(this);
billingService = new BillingService();
}
#Test
public void testGetBilling(){
List<Billing> billings = new ArrayList<>();
...
List<BillingPayload> payloads = new ArrayList<>();
when(billingMapper.convertTo(billings)).thenReturn(payloads);
bilingService.getBilling(1l);
}
}
#Mapper(componentModel="spring")
public interface BillingMapper{
...
}
When I debug and I'm stuck in getBilling method in BillingService Class, billingMapper is alway null;
You are using very strange configuration. First of all you have method returning BillingService that doesn't specify any return value so this would not even compile. I suggest following way:
#Service
public BillingService{
private final BillingRepository billingRepository;
private final BillingMapper billingMapper;
// constructor with bean injection
public BillingService(final BillingRepository billingRepository,
final BillingMapper billingMapper) {
this.billingRepository = billingRepository;
this.billingMapper = billingMapper;
}
public void getBilling(Long billingId){
....
billingMapper.convertTo(...);
}
}
Then you can configure your test like following:
#RunWith(SpringJUnit4ClassRunner.class)
public class BillingServiceTest {
#Spy private BillingMapper billingMapper = Mappers.getMapper(BillingMapper.class);
#MockBean private BillingRepository billingRepository;
#Autowired private BillingService billingService;
#TestConfiguration
static class BillingServiceTestContextConfiguration {
#Bean public BillingService billingService() {
return new BillingService(billingRepository, billingMapper);
}
}
#Test
public void testGetBilling(){
List<Billing> billings = new ArrayList<>();
...
List<BillingPayload> payloads = new ArrayList<>();
when(billingRepository.findById(1L)).thenReturn(); // someResult
when(billingMapper.convertTo(billings)).thenReturn(payloads);
bilingService.getBilling(1l);
}
}
#Mapper(componentModel="spring")
public interface BillingMapper{
...
}
Similiar configuration should work. Main problem is that you are mixing #Autowired with setter/constructor injection (don't know since your weird method inside BillingService. Also dont know why you use #Spy annotation when you are tryning to Mock interface. #Spy is used to mock actual instance, while #Mock is mocking Class type. I would stick with #MockBean private BillingMapper billingMapper instead if BillingMapper is specified as Bean in your application.

Annotation #Value is not working in my service

I have my main configuration
#EnableScheduling
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "gr.citystore.web.helios.yeastar" })
#PropertySource(value = { "classpath:application.properties" })
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private MyServiceImpl myService;
#EventListener(ContextRefreshedEvent.class)
public void contextRefreshedEvent() {
MyThread mThread = new MyThread(myService);
}
}
And my service the code is:
#Service("myService")
#PropertySource(value = { "classpath:application.properties" })
public class MyServiceImpl implements MyService{
#Value("${property.api.ip}")
private String apiIP;
#Value("${property.api.port}")
private String apiPort;
public String myMethod() {
}
}
My problem is that the #Value annotation is not working when I pass it as argument in myThread, instead is return "${property.api.port}".
What I am missing here?
EDIT:
The application.properties file location is "src/main/resources" and the content is:
property.api.ip = 12.34.50.30
property.api.port = 50034
You could just inject the values of the application.properties into your class like this:
#Service("myService")
public class MyServiceImpl implements MyService{
private final String apiIP;
private final String apiPort;
public MyServiceImpl(#Value("${property.api.ip}") String apiIP,
#Value("${property.api.port}") apiPort) {
this.apiIP = apiIP;
this.apiPort = apiPort;
}
public String myMethod() {
}
}

How to mock Feign Client

I can't mock my Feign Client using Mockito.
MyService.class
#Service
#AllArgsConstructor
public class MyService implements IMyService{
private static final Logger LOGGER = LoggerFactory.getLogger(MyService .class);
private final MyRepository repository;
private final MyFeignClient myFeignClient;
#Autowired
private MyDao myDao;
#Override
#Async
public void process(Map<UUID, Long> command) {
DocIds docIds = getDocIds(command.values().stream().findFirst().get());
archiveData(command.keySet().stream().findFirst().get(), documentIds.getIds());
}
private DocumentIds getDocIds(Long retentionId) {
return myFeignClient.getDocumentIds(retentionId);
}
private void archiveData(UUID execId, List<Long> docIds) {
List<MyDocument> myDocs= prepareMyDocs(docIds);
repository.saveAll(myDocs);
}
And my test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class ArchiveServiceTest {
#Autowired
ArchiveService archiveService;
#MockBean
MyDao myDao;
#MockBean
DocRepository archiveRepository;
#MockBean
private MyFeignClient myFeignClient;
#Test
public void shouldReturnTheSameNumberOfDocumentsToArchive() {
//given
List<DocData> documentData = prepareDocumentData();
// doReturn(new DocIds()).when(myFeignClient).getDocumentIds(1000L);
DocumentIds documentIds = new DocumentIds();
documentIds.setIds(Arrays.asList(1L, 2L));
when(myFeignClient.getDocIds(any())).thenReturn(documentIds);
when(documentDataDao.getAllDocumentData(anyList())).thenReturn(documentData);
doNothing().when(archiveRepository.saveAll(any()));
//when
Map<UUID, Long> command = new HashMap<>();
command.put(UUID.randomUUID(), 1000L);
archiveService.process(command);
//then
...
MyFeignClient:
#FeignClient(name = "myFeignClient", url = "${feign.searcher.path}")
public interface MyFeignClient{
#RequestMapping(method = RequestMethod.GET, path = "/document/path/{id}")
DocIds getDocumentIds(#PathVariable("id") Long id);
}
When running a test,
return myFeignClient.getDocumentIds(retentionId);
returns NULL. Why?
I don't have more ideas. I don't want to use WireMock. The same happens with my documentDataDao that doesn't return any values (null) specified in thenReturn() clause.
Have You tried it this way:
Mockito.when(myFeignClient.getDocumentIds(Mockito.eq(1000L))).thenReturn(new DocIds());
In You example, mock is commented out ;)
// doReturn(new DocIds()).when(myFeignClient).getDocumentIds(1000L);
But I'm sure it is just a bug in Your example.

How to set up an automated integration test to check an aspect functionality with Spring-Boot

I've added an AOP (Aspect Oriented Programming) Aspect to my working project. It does work, but it won't be called when trying to Test it's functionality with an Integration Test.
The problem is, that the aspect is not called when the tests runs through. When using it normally it works fine.
I've tried to create a custom context which is supposed to be loaded for the integration tests, as I thought that the Aspect might not be loaded in the default context for these tests.
As this didn't work i also tried to manually proxy the bean of the aspect, but this didn't work neither.
Here's my integration test class:
#ComponentScan(basePackages = { "package.aspects" })
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ZirafyApp.class)
#ContextConfiguration(classes ={ IntegrationTestAOPConfiguration.class })
public class CellResourceIntTest {
private static CellTestHelper helper = new CellTestHelper();
#Autowired
private PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver;
#Autowired
private ExceptionTranslator exceptionTranslator;
#Autowired
private EntityManager em;
#Autowired
private BusinessFacade businessFacade;
#Autowired
private CellRepository cellRepository;
#Autowired
private AspectModule aspectModule;
private MockMvc restCellMockMvc;
private MappingJackson2HttpMessageConverter jacksonMessageConverter = new MappingJackson2HttpMessageConverter();
private Cell cell;
private Cell parentCell;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final CellResource cellResource = new CellResource(cellRepository, businessFacade);
this.restCellMockMvc = MockMvcBuilders.standaloneSetup(cellResource)
.setCustomArgumentResolvers(pageableHandlerMethodArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter).build();
}
#Test
#Transactional
public void update_cellDtoWithEmptyName_returnsHttpError422AndCellInDbIsNotUpdated() throws Exception {
AspectJProxyFactory factory = new AspectJProxyFactory(cellRepository);
factory.addAspect(aspectModule);
CellRepository cellRepository = factory.getProxy();
CellDto cellDtoToUpdate = new CellDto.Builder().id(2).name(null).x(-10).active(true).parent(1).build();
Cell parentCell = helper.createCell(1L);
Cell cellToUpdate = helper.createCell(2L);
cellRepository.saveAndFlush(parentCell);
cellRepository.saveAndFlush(cellToUpdate);
restCellMockMvc.perform(put("/api/cells/update")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(cellDtoToUpdate)))
.andExpect(status().is(200));
Cell updatedCell = cellRepository.findOne(2L);
assertEquals(cellToUpdate.getX(), updatedCell.getX());
}
Here the configuration file for the integration test:
#Configuration
#EnableJpaRepositories(basePackages = {"package.repository"})
#ComponentScan("ch.post.pf.aspects")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class IntegrationTestAOPConfiguration {
#Autowired
private ExceptionTranslator exceptionTranslator;
#Autowired
private EntityManager em;
#Autowired
private CellConverter cellConverter;
#Autowired
private CellTreeService cellTreeService;
#Autowired
private CellService cellService;
#Autowired
private CellRepository cellRepository;
#Autowired
private BusinessFacade businessFacade;
#Autowired
private AspectModule aspectModule;
#Bean
public CellConverter returnCellConverter() {
return cellConverter;
}
#Bean
public AspectModule returnAspectModule() {
return null;//Aspects.aspectOf(AspectModule.class);
}
#Bean
public PageableHandlerMethodArgumentResolver returnPageableArgumentResolver() {
return new PageableHandlerMethodArgumentResolver();
}
#Bean
public ExceptionTranslator returnExceptionTranslator() {
return exceptionTranslator;
}
#Bean
#Primary
public EntityManager returnEntityManager() { return em; }
#Bean
public BusinessFacade returnBusinessFacade() {
return businessFacade;
}
#Bean
public CellTreeService returnCellTreeService() {
return cellTreeService;
}
#Bean
public CellService returnCellService() {
return cellService;
}
}
And here my aspect-file:
#Aspect
#Component
public class AspectModule {
private BusinessFacade businessFacade;
#Autowired
AspectModule(BusinessFacade businessFacade){
this.businessFacade = businessFacade;
}
#Pointcut("execution(* ch.post.pf.web.rest.CellResource.update(..))")
private void update() {}
#Around("update() && args(cell)")
public Object checkIsValidCell(ProceedingJoinPoint pjp, CellDto cell) {
System.out.println("Aspect was run");
final String message = canUpdate(cell);
if (message.equals("cell_valid")) {
try {
return pjp.proceed(); // Calls the usual update() function, if the cell is valid
} catch (Throwable e) {
System.out.println("Something went wrong with the aspects");
System.out.println(e.toString());
return null;
}
} else {
deleteIfCellWasEmpty(cell);
return ResponseUtil.unprocessableEntity(message);
}
}
}
The aspect should keep working as it does right now but it should also work in the integration tests, at the moment it isn't called at all inside those.

Spring injection: #MockBean #Repository is not injected

I'm trying to #MockBean a #Repository annotated class:
#Repository
public interface ApplicationDao extends MongoRepository<Application, String> {}
I'm injecting it into a #Service annotated class:
#Service
public class AuthorizationService {
private ApplicationDao appsDao;
private List<Application> allowedApplications;
#Autowired
public AuthorizationService(ApplicationDao appsDao) {
this.appsDao = appsDao; //<<MOCKED INJECTED BEAN>>
this.fillApplications();
}
private void fillApplications() {
this.appsDao.findAll() //<<MOCKED method>>
.forEach(entry -> {
this.allowedApplications.put(entry.getName(), entry);
});
}
public bool isAuthorized(Application application) {
return this.allowedApplications
.stream()
.anyMatch(app -> app.getId().equals(application.getId()));
}
}
My test mocking configuration looks like:
#RunWith(SpringRunner.class)
#SpringBootTest()
public class GroupReferencesTest {
private #Autowired AuthorizationService;
private #MockBean ApplicationDao applicationDao;
#Before
public void setUp() {
Application testApplication = new Application();
testApplication.setName("test-application");
List<Application> allowedApplications = new ArrayList<Application>();
allowedApplications.add(testApplication);
Mockito
.when(this.applicationDao.findAll())
.thenReturn(allowedApplications);
}
#Test
public void test() {
Application app = new Application();
app.getId("test-application");
assertTrue(this.authorizationService.isAuthorized(app)); //<<FAILS>>
}
}
Nevertheless, my mocked object is not injected. I mean, when my AuthorizationService calls its injected ApplicationDao is returns an empty list instead of my mocked list.
I've tried to use #MockBean(name="applicationDao") as well. The behavior is the same.
I've also tried to configure my mocked bean using this code:
#TestConfiguration
public class RestTemplateTestConfiguration {
#Bean("applicationDao")
#Primary
public static ApplicationDao mockApplicationDao() {
ApplicationDao mock = Mockito.mock(ApplicationDao.class);
Application testApplication = new Application();
testApplication.setName("test-application");
List<Application> allowedApplications = new ArrayList<Application>();
allowedApplications.add(testApplication);
Mockito
.when(mock.findAll())
.thenReturn(allowedApplications);
return mock;
}
}
However, it doesn't works right.
Application class is:
public class Application {
private String id;
//setters & getters
}
Any ideas?
First things first - the type of test. Answer: Unit test.
You are starting Spring context that manages a lifecycle of AuthorizationService and then you are trying to inject mock. What really happens is that Spring IoC container is injecting a real ApplicationDao (the one managed by Spring IoC container) into the AuthorizationService.
Solution:
Manage lifecyle of AuthorizationService by your test runner (like MockitoJUnitRunner and inject ApplicationDao mock into it):
#RunWith(MockitoJUnitRunner.class)
public class GroupReferencesTest {
private #InjectMocks AuthorizationService authorizationService;
private #Mock ApplicationDao applicationDao;
#Before
public void setUp() {
Application testApplication = new Application();
testApplication.setName("test-application");
List<Application> allowedApplications = new ArrayList<Application>();
allowedApplications.add(testApplication);
Mockito
.when(this.applicationDao.findAll())
.thenReturn(allowedApplications);
}
#Test
public void test() {
Application app = new Application();
app.getId("test-application");
assertTrue(this.authorizationService.isAuthorized(app));
}
}
Working example
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {AuthorizationService.class})
public class GroupReferencesTest {
#Autowired
private AuthorizationService;
#MockBean
private ApplicationDao applicationDao;
#Test
public void test() {
//given
Mockito.when(applicationDao.findAll()).thenReturn(emptyList());
//when & then
assertTrue(authorizationService.isAuthorized(app));
}
}

Resources