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

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
}

Related

How to test multiple instance creation of a bean of one class filled with values from application.properties?

After creating multiple beans of a specific class using #PostConstruct according to Multiple instances of a bean of one class filled with values from application.properties
I wonder how I could test the creation of those bean instances.
I tried it this way:
#ActiveProfiles("test")
#RequiredArgsConstructor
#ExtendWith({SpringExtension.class, MockitoExtension.class})
#SpringBootTest(classes = {AppHealthCheckContributor.class, AppHealthCheckProperties.class})
#ConfigurationProperties(prefix = "healthcheck")
#ContextConfiguration(classes = { AppHealthCheckConfig.class })
//#TestPropertySource(properties = "healthcheck.app[0].name=testHealthIndicator, healthcheck.app[0].baseUrl=http://localhost, healthcheck.app[0].basePath=/test")
#TestPropertySource(locations = {"/application-test.properties"})
class AppHealthCheckConfigTest {
#Autowired
private AdapterHealthCheckConfig config;
#Test
void healthCheckBeansAreInitialized() {
// config.init();
System.out.println(config.getApps().get(0));
}
}
but this results in: AppHealthCheckProperties(baseUrl=null, basePath=null, name=null)
Try the following:
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties(value = AdapterHealthCheckConfig.class)
#TestPropertySource("classpath:application-test.properties")
class AppHealthCheckConfigTest {
#Autowired
private AdapterHealthCheckConfig config;
#Test
void healthCheckBeansAreInitialized() {
// config.init();
System.out.println(config.getApps().get(0));
}
}

ApplicationContext returns null in SpringBootTest run with SpringRunner.class

I have a problem of running my Test class. It returns "org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 't.c.i.s.se.Sfts' available: expected single matching bean but found 2: sftsImpl,sfts" this exception after I run it.
Here's my test class;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Sfta.class)
public class SftaTests {
#Autowired
ApplicationContext ac;
#Test
public void contextLoads() {
Sfts sfts= ac.getBean(Sfts.class);
assertTrue(Sfts instanceof SftsImpl);
}
}
And my other classes are like;
public interface Sfts {
public void process();
}
#Service
#Component
public class SftsImpl implements Sfts {
#Autowired
private GlobalConfig globalConfig;
#Autowired
private Ftr ftr;
private Fc fc;
#Async
#Scheduled(initialDelayString = "${s.f.t.m}", fixedRateString = "${s.f.t.m}")
public void process() {
int hod = DateTime.now().getHourOfDay();
if (hod != 6){
fc = new Fc(globalConfig, ftr);
fc.control();
}
}
}
Why I get the error after running the test application?
Try to remove #Component annotation from the SftsImpl bean.
#Service is enough to register a bean.
Also if you just want to test your bean - getting it from ApplicationContext maybe is not the best option.
Code example of a unit test without using ApplicationContext:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Sfta.class)
public class SftaTests {
#Autowired
Sfts sfts;
#Test
public void testAsync() {
sfts.process();
// do assertions here
}
}

How to autowire beans in test class when using #SpringBootTest

I have an integration test class annotated with #SpringBootTest which starts up the full application context and lets me execute my tests. However I am unable to #Autowired beans into the test class itself. Instead I get an error:
No qualifying bean of type 'my.package.MyHelper' available".
If I do not #Autowire my helper class, but keep the code directly inside the setUp function, the test works as expected.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
public class CacheControlTest {
#Autowired
private MyHelper myHelper;
#Before
public void setUp() {
myHelper.doSomeStuff();
}
#Test
public void test1() {
// My test
}
}
How can I make use of Spring autowiring inside the test class while also using #SpringBootTest?
Following #user7294900 advice below, creating a separate #Configuration file and adding this at the top of CacheControlTest works:
#ContextConfiguration(classes = { CacheControlTestConfiguration.class })
However is there any way of keeping the configuration inside the CacheControlTest class itself? I have tried adding inside my test class:
public class CacheControlTest {
#TestConfiguration
static class CacheControlTestConfiguration {
#Bean
public MyHelper myHelper() {
return new MyHelper();
}
}
}
And
public class CacheControlTest {
#Configuration
static class CacheControlTestConfiguration {
#Bean
public MyHelper myHelper() {
return new MyHelper();
}
}
}
But they do not seem to have any effect. I still get the same error. The same configuration block works when placed in an separate file as mentioned above though.
Add ContextConfiguration for your Test Class:
#ContextConfiguration(classes = { CacheControlTestConfiguration.class })

Excluding an ApplicationListener #Component in Spring Boot during tests

I am trying to have my test unit up and running, and I have encountered a weird issue. My application uses an ApplicationListener class annotated as a #Component to perform an operation during startup.
During tests I have mocked the service that contains the logic, but I found that even though Mockito's when instructions work well in controller scope, the bean is not initialized for this ApplicationListener class: instead of returning what I define in the test unit, it returns either false or null - depending on the data type returned by each method in the service.
Since I have not found any way to initialize the mocked service from the test unit for the ApplicationListener class, I have decided to exclude it. To do so I have tried different approaches, being the one most often used that of creating a test application context and change its configuration. Unfortunately, nothing I have seen is working - so I am here asking for help. If possible, I would prefer not touching the ApplicationListener class and do all related coding in the test code.
I am interested in any of the two possible solutions, if they can be done:
1.- Get the mocked behaviour during the ApplicationListener execution, but I have read somewhere that this cannot be done
2.- Exclude the #Component from the test unit somehow.
TestUnit.Java:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestConfigurationService {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MockService mockService;
private void initMockBean () throws Exception {
when(mockService.isDoingSomething()).thenReturn(true);
}
#Before
public void setup() throws Exception {
// Spring mock context application setup
this.mockMvc = webAppContextSetup(webApplicationContext).build();
// Initialize ConsulService mock bean
initMockBean ();
}
}
TestApplication.java
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages="my.base.package", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = StartupConfiguration.class))
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Besides what is shown in the code, I have also tried this annotation in file TestApplication.java:
#SpringBootApplication(exclude={StartupConfiguration.class})
StartupConfiguration.java
#Component
public class StartupConfiguration implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ConfigurationService configurationService;
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
configurationService.updateConfiguration();
} catch (Exception e) {
throw new RuntimeException ("Error", e);
}
}
}
ConfigurationService.java
public interface ConfigurationService {
public void updateConfiguration () throws Exception;
}
ConfigurationServiceImpl.java
#Service
#Transactional
public class ConfigurationServiceImpl implements ConfigurationService {
#Autowired
private MService mockService;
#Override
public void updateConfiguration() throws Exception {
if (mockService.isDoingSomething()==false)
throw new Exception ("Something went wrong");
}
}
Versions:
Spring Boot 1.5.4.RELEASE,
Java 1.8
You can create mock bean of the same type and mark it with #Primary annotation to replace real bean. You can achieve this by having test such configuration:
#Configuration
#Import(TestApplication.class)
public class TestConfiguration {
#Bean
#Primary
public ConfigurationService configurationService() {
return Mockito.mock(ConfigurationService.class);
}
}
then get this mock in test:
...
public class TestConfigurationService {
...
#Autowired
ConfigurationService configurationService;
#Before
public void setUp() {
when(mockService.isDoingSomething()).thenReturn(true);
}
}
Thanks, araxn1d. Your answer gave me the clue to solve this issue.
I mocked the StartupConfiguration class in TestUnit.java:
#MockBean
private StartupConfiguration startupConfiguration;
Though in this case I was lucky: application listeners don't have returning methods, so they don't need when test configuration. If I had required that some method there returned for example true or a value, this method would not apply.
But at least for application listeners, this is enough.

How to fix test failing with "No ModelAndView found"?

This class is in the top of my tests hierarchy:
#TestPropertySource("/test.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public abstract class ApplicationAbstractTest {
}
And few more test classes:
#WebAppConfiguration
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#PostConstruct
private void postConstruct() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
JsonUserServiceTest:
#ActiveProfiles("json")
public class JsonUserServiceTest extends ApplicationAbstractTest {
#Before
public void setUp() throws Exception {
...
}
}
ContactControllerTest:
public class ContactControllerTest extends AbstractControllerTest {
#Test
public void testGet() throws Exception {
mockMvc.perform(get("/update-" + ID + "-contact")
.with(userAuth(USER)))
// .andExpect(status().isOk())
.andDo(print())
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
}
}
So, when I run ContactControllerTest along - it is successfull, and print method shows me:
Handler:
Type = com.telecom.web.ContactController
Method = public java.lang.String com.myApp.web.ContactController.details(java.lang.Integer,org.springframework.ui.ModelMap)
But when I run all tests, so JsonUserServiceTest runs first, ContactControllerTest fails. And print shows:
Handler:
Type = null
...
java.lang.AssertionError: No ModelAndView found
What is wrong in configuration? Or how troubleshoot it?
UPD:
at the same time, test like this, allways works fine:
public class UserControllerTest extends AbstractControllerTest {
#Test
public void testRegister() throws Exception {
mockMvc.perform(get("/register"))
.andDo(print())
.andExpect(view().name("profile"))
.andExpect(forwardedUrl("/WEB-INF/jsp/profile.jsp"));
}
}
UPD:
There is controller's method I'm testing:
#GetMapping("/update-{id}-contact")
public String details(#PathVariable Integer id, ModelMap model) {
Integer userId = AuthorizedUser.id();
LOG.info("get contact {} for User {}", id, userId);
Contact contact = service.get(id, userId);
model.addAttribute("contact", contact);
return "details";
}
I also have such bean:
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
UPD: I've tried configure mockMvc in separate class:
#Configuration
public class TestConfig {
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
And added it here:
#WebAppConfiguration
#ContextConfiguration(classes = {TestConfig.class})
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
but I've received:
java.lang.IllegalStateException: springSecurityFilterChain cannot be
null. Ensure a Bean with the name springSecurityFilterChain
implementing Filter is present or inject the Filter to be used.
The WARN message doesn't cause the test cases to fail. It just says that Entity manager factory is registered twice. This will only be an issue if you cluster your application using the same Entity Manager Factory. For test case run it is not a cause for concern.
The root cause of the testcase failure is in these two lines
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
Please check if the project has a view named "details" and the forwardded url is "/WEB-INF/jsp/details.jsp"
Update
Could you please try this
#Configuration
public class TestConfig {
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurityFilterChain)
.build();
}
}
Create a configuration file that will initialize mocking objects for your test cases. And put at all test case classes.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class})
It will initialize all your mocking objects only once and cached after that and reused for all test cases.
Or if you don't want to use mocking configuration, you can directly
pass the actual application configuration to ContextConfiguration as
below
For annotation based application configuration (here AppConfig and AppConfig2 are your configuration class)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {AppConfig.class, AppConfig2.class})
For xml based application configuration (here appConfig.xml and appConfig2.xml are your configuration files)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:pathTo/appConfig.xml","classpath:pathTo/appConfig2.xml"})
Reference : JUnit + Spring integration example

Categories