How to get spring context from maven module - java

I'm having an issue trying to make test in my spring-boot project.
architecture
as you can see, my project is devided with maven modules.
"alta-launcher" is the "main" project getting every other module as dependencies.
The problem is that my tests in the user module won't get the spring context so my fields "Autowired" will be null on runtime.
Any ideas how to configure this project to be able to do tests in each modules ?
Edit :
#SpringBootTest(classes = CoreApplication.class)
class UserQueryTransformerImplTest {
#Autowired
private UserQueryTransformer userQueryTransformer;
#Test
void toDTO() {
UserEntity userEntity = new UserEntity();
userEntity.setLogin("everest");
userEntity.setFirstName("Everest");
userEntity.setLastName("Mountain");
UserQueryDTO userQueryDTO = userQueryTransformer.toDTO(userEntity);
assertEquals(userEntity.getLogin(), userQueryDTO.getLogin());
assertEquals(userEntity.getFirstName(), userQueryDTO.getFirstName());
assertEquals(userEntity.getLastName(), userQueryDTO.getLastName());
}
The annotation #SpringBootTest with the attribute classes is unusable because I don't have access to the launcher module from user module. And without the attribute (juste #SprinBootTest) is when I'm getting my autowire field null which makes sense cause I don't have the context.

I was able to fix the issue by using ContextConfiguration annotation.
(ExtendWith is for the junit5 part)
#ContextConfiguration(classes = ConfigurationTest.class)
#ExtendWith(SpringExtension.class)
class UserQueryTransformerImplTest {
#Autowired
private UserQueryTransformer userQueryTransformer;
#Test
void toDTO() {
UserEntity userEntity = new UserEntity();
userEntity.setLogin("everest");
userEntity.setFirstName("Everest");
userEntity.setLastName("Mountain");
UserQueryDTO userQueryDTO = userQueryTransformer.toDTO(userEntity);
assertEquals(userEntity.getLogin(), userQueryDTO.getLogin());
assertEquals(userEntity.getFirstName(), userQueryDTO.getFirstName());
assertEquals(userEntity.getLastName(), userQueryDTO.getLastName());
}
and here is my configurationTest.java
#TestConfiguration
public class ConfigurationTest {
#Bean
UserQueryTransformer createUserQueryTransformer() {
return new UserQueryTransformerImpl();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
UserQueryDTO createUserQueryDTO() {
return new UserQueryDTO();
}

You can recreate your custom context in tests, try something like this:
#ContextConfiguration
#RunWith(SpringRunner.class)
class UserQueryTransformerImplTest {
#TestConfiguration
static class Config {
#Bean
public UserQueryTransformer uqt() {
return new UserQueryTransformer();
}
}
#Autowired
private UserQueryTransformer userQueryTransformer;
#Test
void toDTO() {
UserEntity userEntity = new UserEntity();
userEntity.setLogin("everest");
userEntity.setFirstName("Everest");
userEntity.setLastName("Mountain");
UserQueryDTO userQueryDTO = userQueryTransformer.toDTO(userEntity);
assertEquals(userEntity.getLogin(), userQueryDTO.getLogin());
assertEquals(userEntity.getFirstName(), userQueryDTO.getFirstName());
assertEquals(userEntity.getLastName(), userQueryDTO.getLastName());
}

Related

Configuration Getting Ignored in Test

I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}

Spring Boot maven multimodule project - unit testing (application context)

I am beginner with spring framework. I have a problem with configuring unit tests in spring boot, more precisely with loading spring context while running unit tests. I work with maven multimodule project (in team) and looking for the right solution to do this.
Part of my project structure is as follows:
commons (module, packaging:jar, utils module)
+--- src
+--- pom.xml
proposal (module, packaging:pom)
proposal-api (submodule: interfaces, dto, packaging:jar)
proposal-mapping (submodule: entities)
proposal-service (submodule: services, spring data repositories, dto - entity<->dto mappers, depends on proposal-api and proposal-mapping packaging:jar)
+--- src
  +---main
    +--- java
      +---com.company.proposal.service
        +--- DeviceRepositoryService.java
        +---
DeviceMapper.java
        +---
ProposalRepositoryService.java
        +---
ProposalMapper.java
        +---
and much more classes...
  +--- test
    +--- java
      +---com.company.proposal.service
        +---
DeviceRepositoryServiceTest.java
        +---
ProposalRepositoryServiceTest.java
        +---
...
+--- pom.xml
proposal-starter (submodule: autoconfiguration classes, packaging:jar)
+--- src
  +---main
    +--- java
      +---com.company.proposal.configuration
        +--- ProposalAutoConfiguration.java
        +--- RemoteReportProcessorAutoConfiguration.java
        +--- other configuration classes...
    +---resources
      +---META-INF
        +--- spring.factories
      +---application.properties
+--- pom.xml
entry-point (module, packaging: pom)
entry-point-api (submodule, packaging: jar)
entry-point-service (submodule, packaging: jar)
entry-point-starter (submodule, packaging: war deployed on wildfly)
other-modules ...
pom.xml (root pom)
Example unit test written by me (DeviceRepositoryServiceTest.java):
#RunWith(SpringRunner.class)
public class DeviceRepositoryServiceTest {
#Rule
public ExpectedException thrown = ExpectedException.none();
#MockBean
private DeviceRepository deviceRepository;
#Autowired
private DeviceMapper deviceMapper;
private DeviceRepositoryService deviceRepositoryService;
private final String imei = "123456789123456";
private final String producer = "samsung";
private final String model = "s5";
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
deviceRepositoryService = new DeviceRepositoryService(deviceRepository, deviceMapper);
}
#org.springframework.boot.test.context.TestConfiguration
static class TestConfiguration {
#Bean
public DeviceMapper deviceMapper() {
return new DeviceMapperImpl();
}
}
#Test
public void test_should_create_device() {
given(deviceRepository.findByImei(imei)).willReturn(null);
when(deviceRepository.save(any(Device.class))).thenAnswer((Answer) invocation -> invocation.getArguments()[0]);
DeviceSnapshot device = deviceRepositoryService.createOrFindDeviceByImei(imei, producer, model);
assertThat(device.getImei()).isEqualTo(imei);
assertThat(device.getProducer()).isEqualTo(producer);
assertThat(device.getModel()).isEqualTo(model);
verify(deviceRepository, times(1)).save(any(Device.class));
}
#Test
public void test_should_return_device() {
Device testDevice = createTestDevice();
given(deviceRepository.findByImei(imei)).willReturn(testDevice);
DeviceSnapshot actualDevice = deviceRepositoryService
.createOrFindDeviceByImei(testDevice.getImei(), testDevice.getProducer(), testDevice.getModel());
assertThat(actualDevice.getImei()).isEqualTo(testDevice.getImei());
assertThat(actualDevice.getProducer()).isEqualTo(testDevice.getProducer());
assertThat(actualDevice.getModel()).isEqualTo(testDevice.getModel());
verify(deviceRepository, times(0)).save(any(Device.class));
verify(deviceRepository, times(1)).findByImei(testDevice.getImei());
}
#Test
public void test_should_find_device() {
Device device = createTestDevice();
given(deviceRepository.findOne(device.getId())).willReturn(device);
DeviceSnapshot actualDevice = deviceRepositoryService.findDeviceById(device.getId());
DeviceSnapshot expectedDevice = deviceMapper.toDeviceSnapshot(device);
assertThat(actualDevice).isEqualTo(expectedDevice);
verify(deviceRepository, times(1)).findOne(device.getId());
}
#Test
public void test_should_find_device_by_pparams() {
Device device = createTestDevice();
Long proposalId = 1L, providerConfigId = 2L;
given(deviceRepository.findByProposalParams(proposalId, providerConfigId)).willReturn(device);
DeviceSnapshot actualDevice = deviceRepositoryService.findDeviceByProposalParams(proposalId, providerConfigId);
DeviceSnapshot expectedDevice = deviceMapper.toDeviceSnapshot(device);
assertThat(actualDevice).isEqualTo(expectedDevice);
verify(deviceRepository, times(1)).findByProposalParams(proposalId, providerConfigId);
}
#Test
public void test_should_throw_not_found_1() {
given(deviceRepository.findOne(anyLong())).willReturn(null);
this.thrown.expect(DeviceNotFoundException.class);
deviceRepositoryService.findDeviceById(1L);
}
#Test
public void test_should_throw_not_found_2() {
given(deviceRepository.findByProposalParams(anyLong(), anyLong())).willReturn(null);
this.thrown.expect(DeviceNotFoundException.class);
deviceRepositoryService.findDeviceByProposalParams(1L, 1L);
}
private Device createTestDevice() {
return Device.builder()
.id(1L)
.imei(imei)
.model(model)
.producer(producer)
.build();
}
}
As you can see I use #TestConfiguration annotation to define context, but because class DeviceRepositoryService is quite simple - only 2 dependencies so context definition is also simple. I also have to test class ProposalRepositoryService which looks as follows in short:
#Slf4j
#Service
#AllArgsConstructor
#Transactional
public class ProposalRepositoryService implements ProposalService {
private final ProposalRepository proposalRepository;
private final ProposalMapper proposalMapper;
private final ProposalRepositoryProperties repositoryProperties;
private final ImageProposalRepository imageProposalRepository;
private final ProviderConfigService providerConfigService;
...
}
In above class is more dependencies and the thing is I don't want to write bunch of configuration code for every test (TestConfiguration annotation). Eg. If I add some dependency to some service I would have to change half of my unit tests classes, also a lot of code repeats itself. I have also example when unit test code is getting ugly because of configuration definition:
#TestPropertySource("classpath:application-test.properties")
public class RemoteReportProcessorRepositoryServiceTest {
#Autowired
private RemoteReportProcessorRepositoryService remoteReportProcessorRepositoryService;
#TestConfiguration //here, I don't want to write bunch of configuration code for every test
static class TestConfig {
#Bean
#Autowired
public RemoteReportProcessorRepositoryService remoteReportProcessorRepositoryService(RemoteReportMailService remoteReportMailService,
FtpsService ftpsService,
RemoteDailyReportProperties remoteDailyReportProperties,
RemoteMonthlyReportProperties remoteMonthlyReportProperties,
DeviceRepository deviceRepository,
ProposalRepository proposalRepository) {
return new RemoteReportProcessorRepositoryService(ftpsService, remoteReportMailService, remoteDailyReportProperties, remoteMonthlyReportProperties, deviceRepository, proposalRepository);
}
#Bean
#Autowired
public FtpsManagerService ftpsManagerService(FTPSClient ftpsClient, MailService mailService, FtpsProperties ftpsProperties) {
return new FtpsManagerService(ftpsClient, ftpsProperties, mailService);
}
#Bean
public FTPSClient ftpsClient() {
return new FTPSClient();
}
#Bean
#Autowired
public MailService mailService(MailProperties mailProperties, JavaMailSender javaMailSender, PgpProperties pgpProperties) {
return new MailManagerService(mailProperties, javaMailSender, pgpProperties);
}
#Bean
public JavaMailSender javaMailSender() {
return new JavaMailSenderImpl();
}
#Bean
#Autowired
public RemoteReportMailService remoteReportMailService(RemoteReportMailProperties remoteReportMailProperties,
JavaMailSender javaMailSender,
Session session,
PgpProperties pgpProperties) {
return new RemoteReportMailManagerService(remoteReportMailProperties, javaMailSender, session, pgpProperties);
}
#Bean
#Autowired
public Session getJavaMailReceiver(RemoteReportMailProperties remoteReportMailProperties) {
Properties properties = new Properties();
properties.put("mail.imap.host", remoteReportMailProperties.getImapHost());
properties.put("mail.imap.port", remoteReportMailProperties.getImapPort());
properties.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
properties.setProperty("mail.imap.socketFactory.fallback", "false");
properties.setProperty("mail.imap.socketFactory.port", remoteReportMailProperties.getImapPort().toString());
properties.put("mail.imap.debug", "true");
properties.put("mail.imap.ssl.trust", "*");
return Session.getDefaultInstance(properties);
}
}
...
}
So, my question is how to configure spring context for unit testing in spring boot maven multimodule project the right way, without writing bunch of configuration code?
I also will be grateful for the links to the articles when is describe in detail how to deal with maven multimodule projects.
After reading various articles and posts eg. Is it OK to use SpringRunner in unit tests? I realized that I don't need the entire application context when running tests, instead I should mock bean dependencies using plain #Mock annotation if testing without even involving and loading spring application context (which is faster). However, If I need some slice of application context (eg. to automatically load test properties or just for integration tests)
then I use spring boot annotations prepared for that: #WebMvcTest #JpaTest #SpringBootTest and so on.
Examples:
Plain Mock Test (without involving spring):
public class UserServiceImplTest {
#Mock
private UserRepository userRepository;
private UserServiceImpl userService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
userService = new UserServiceImpl(userRepository);
}
/* Some tests here */
}
Test with slice of spring context:
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#EnableConfigurationProperties(value = DecisionProposalProperties.class)
#SpringBootTest(classes = {
DecisionProposalRepositoryService.class,
DecisionProposalMapperImpl.class
})
public class DecisionProposalRepositoryServiceTest {
#MockBean
private DecisionProposalRepository decisionProposalRepository;
#MockBean
private CommentRepository commentRepository;
#Autowired
private DecisionProposalRepositoryService decisionProposalRepositoryService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
/* Some tests here */
}
Data jpa test:
#RunWith(SpringRunner.class)
#DataJpaTest
public class ImageProposalRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private ImageProposalRepository imageProposalRepository;
#Test
public void testFindOne() throws Exception {
ImageProposal imageProposal = ImageProposal.builder()
.size(1024)
.filePath("/test/file/path").build();
entityManager.persist(imageProposal);
ImageProposal foundImageProposal = imageProposalRepository.findOne(imageProposal.getId());
assertThat(foundImageProposal).isEqualTo(imageProposal);
}
}

How to test #Cacheable?

I am struggling with testing #Cacheable within a Spring Boot Integration Test. This is my second day learning how to do Integration Tests and all of the examples I have found use older versions. I also saw an example of assetEquals("some value", is()) but nothing with an import statement to know which dependency "is" belongs to. The test fails at the second
This is my integration test....
#RunWith(SpringRunner.class)
#DataJpaTest // used for other methods
#SpringBootTest(classes = TestApplication.class)
#SqlGroup({
#Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
scripts = "classpath:data/Setting.sql") })
public class SettingRepositoryIT {
#Mock
private SettingRepository settingRepository;
#Autowired
private Cache applicationCache;
#Test
public void testCachedMethodInvocation() {
List<Setting> firstList = new ArrayList<>();
Setting settingOne = new Setting();
settingOne.setKey("first");
settingOne.setValue("method invocation");
firstList.add(settingOne);
List<Setting> secondList = new ArrayList<>();
Setting settingTwo = new Setting();
settingTwo.setKey("second");
settingTwo.setValue("method invocation");
secondList.add(settingTwo);
// Set up the mock to return *different* objects for the first and second call
Mockito.when(settingRepository.findAllFeaturedFragrances()).thenReturn(firstList, secondList);
// First invocation returns object returned by the method
List<Setting> result = settingRepository.findAllFeaturedFragrances();
assertEquals("first", result.get(0).getKey());
// Second invocation should return cached value, *not* second (as set up above)
List<Setting> resultTwo = settingRepository.findAllFeaturedFragrances();
assertEquals("first", resultTwo.get(0).getKey()); // test fails here as the actual is "second."
// Verify repository method was invoked once
Mockito.verify(settingRepository, Mockito.times(1)).findAllFeaturedFragrances();
assertNotNull(applicationCache.get("findAllFeaturedFragrances"));
// Third invocation with different key is triggers the second invocation of the repo method
List<Setting> resultThree = settingRepository.findAllFeaturedFragrances();
assertEquals(resultThree.get(0).getKey(), "second");
}
}
ApplicationContext, components, entities, repositories and service layer for tests. The reason why I do it this way is because this maven module is used in other modules as a dependency.
#ComponentScan({ "com.persistence_common.config", "com.persistence_common.services" })
#EntityScan(basePackages = { "com.persistence_common.entities" })
#EnableJpaRepositories(basePackages = { "com.persistence_common.repositories" })
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Cache config....
#Configuration
#EnableCaching
public class CacheConfig {
public static final String APPLICATION_CACHE = "applicationCache";
#Bean
public FilterRegistrationBean registerOpenSessionInViewFilterBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
registrationBean.setFilter(filter);
registrationBean.setOrder(5);
return registrationBean;
}
#Bean
public Cache applicationCache() {
return new GuavaCache(APPLICATION_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.DAYS)
.build());
}
}
The repository under test....
public interface SettingRepository extends JpaRepository<Setting, Integer> {
#Query(nativeQuery = true, value = "SELECT * FROM Setting WHERE name = 'featured_fragrance'")
#Cacheable(value = CacheConfig.APPLICATION_CACHE, key = "#root.methodName")
List<Setting> findAllFeaturedFragrances();
}
The first problem with SettingRepositoryIT is, the #Mock anotation on the field settingRepository. This is paradox for any normal-test, integration-test or any else.
You should let Spring bring in the dependencies for the class-under-test, which is SettingRepository in your case.
Please look at this example how #Autowired is used for the class-under-test, which is OrderService in this example:
#RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
#ContextConfiguration
public class OrderServiceTest {
#Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
#Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
#Autowired
private OrderService orderService;
#Test
public void testOrderService() {
// test the orderService
}
}
Go for the documentation with the full example: § 15. Integration Testing
The second problem is that you do not have to test #Cachable. You should only test your implementation. Here is a very good example from Oliver Gierke on how you should test it: How to test Spring's declarative caching support on Spring Data repositories?
In my case I wanted to validate the expression in the unless expression in the #Cacheable annotation, so I think it makes perfect sense and I'm not testing Spring's code.
I managed to test it without using Spring Boot, so it is plain Spring test:
#RunWith(SpringRunner.class)
#ContextConfiguration
public class MyTest {
private static MyCacheableInterface myCacheableInterfaceMock = mock(MyCacheableInterface.class);
#Configuration
#EnableCaching
static class Config {
#Bean
public MyCacheableInterface myCacheableInterface() {
return myCacheableInterfaceMock;
}
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("myObject");
}
}
#Autowired
private MyCacheableInterface myCacheableInterface;
#Test
public void test() {
when(myCacheableInterfaceMock.businessMethod(anyString())).then(i -> {
List<MyObject> list = new ArrayList<>();
list.add(new MyObject(new Result("Y")));
return list;
});
myCacheableInterface.businessMethod("test");
verify(myCacheableInterfaceMock).businessMethod(anyString());
myCacheableInterface.businessMethod("test");
verifyNoMoreInteractions(myCacheableInterfaceMock);
}
}
In MyCacheableInterface I have the following annotation:
public interface MyCacheableInterface {
#Cacheable(value = "myObject", unless = "#result.?[Result.getSuccess() != 'Y'].size() == #result.size()")
List<MyObject> businessMethod(String authorization);
}

How to not connect to service when testing with Spring?

I have an application built with JHipster which contains several tests.
I created a simple configuration class that instantiate a bean connected to an external service as such :
#Configuration
public class KurentoConfiguration {
#Bean(name = "kurentoClient")
public KurentoClient getKurentoClient(#Autowired ApplicationProperties applicationProperties) {
return KurentoClient.create(applicationProperties.getKurento().getWsUrl());
}
}
But as you would guess, this code crash during testing because the external service is not up but this code is still run during application context loading.
So I need to create a "stateless" version of this bean to be used during testing.
Here is a simple example of a test that fail because of my configuration :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
}
#Test
public void getAllLogs()throws Exception {
restLogsMockMvc.perform(get("/management/logs"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
}
}
What is the solution to make this bean not highly dependent of an external service during unit testing ?
You can use the MockBean annotation in your test to replace your existing bean :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
#MockBean
private KurentoClient kurentoClient;
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
given(kurentoClient.someCall()).willReturn("mock");
}
....
}
Here is the Spring Boot documentation :
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans
Thanks to the help of everyone here, I managed to solve this problem :
I created an interface of KurentoClient and implemented a proxy that call KurentoClient methods
My "normal" #Bean kurentoClient returns the implemented proxy
I writed a #TestConfiguration (UnitTestConfiguration) and added a #Bean with the same signature as the one crated above but this one returns mockito's mock(KurentoClient.class)
I created a class TestBase that every test class extends and which
contains
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
#ContextConfiguration(classes = UnitTestConfiguration.class)
public class TestBase {
}

How to use custom Spring scope with unit tests (SpringJUnit4ClassRunner)

I am using JUnit tests with Spring configuration defined in a class annotated with #Configuration in my JUnit Test. The tests looks like this:
#ContextConfiguration(classes = MyConfiguration.class})
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class SomeIntegrationTest {
#Autowired
private MyConfiguration myConfiguration;
#Test
public void someTest() throws Exception {
myConfiguration.myBean();
}
}
In MyConfiguration, I would like to use Spring scope SimpleThreadScope:
#Configuration
public class MyConfiguration {
#Bean
#Scope("thread")
public MyBean myBean() {
return new MyBean();
}
}
When I run the test, the scope is not registered, though. I get
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: java.lang.IllegalStateException: No Scope registered for scope 'thread'
I am aware how a custom scope can be registered programatically:
context.getBeanFactory().registerScope("thread", new SimpleThreadScope());
and I would like to avoid using XML Spring configuration.
Is there any way, how can I register the custom scope in the unit test?
Check this execution listener:
public class WebContextTestExecutionListener extends
AbstractTestExecutionListener {
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
ConfigurableListableBeanFactory beanFactory = context
.getBeanFactory();
Scope requestScope = new SimpleThreadScope();
beanFactory.registerScope("request", requestScope);
Scope sessionScope = new SimpleThreadScope();
beanFactory.registerScope("session", sessionScope);
Scope threadScope= new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
}
}
}
in the test you can put this
#ContextConfiguration(classes = MyConfiguration.class})
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#TestExecutionListeners( { WebContextTestExecutionListener.class})
public class UserSpringIntegrationTest {
#Autowired
private UserBean userBean;
//All the test methods
}

Categories