ServletContextAware bean getting initialized too early when using #SpringBootTest with #AutoConfigureMockMvc - java

I'm seeing an odd issue when using #SpringBootTest with #AutoConfigureMockMvc. I have a SpringBoot project setup with an application.yaml file that provides a custom server.servlet.context-path, this value is used in a platform layer, using a ServletContextAware bean, to get context information for creating an Open API specification. When the application is launched normally, everything works as expected. The ServletContext is updated with context path in ServerProperties.setContextPath(), then setServletContext() is called on my bean. When it is run via a JUnit, this happens in reverse order. I first see setServletContext() called on my bean before the application.yaml data is loaded and I do not get the updated context path.
Spring Boot App
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
application.yaml
server.servlet.context-path: /some/custom/path
Servlet context aware configuration bean
#Configuration
#EnableSwagger2
public class SwaggerConfiguration implements ServletContextAware {
...
public void setServletContext(#NonNull ServletContext servletContext) {
this.servletContext = servletContext;
}
}
JUnit
#SpringBootTest(classes = {Application .class})
#AutoConfigureMockMvc
public class ApplicationTest {
#Autowired
private MockMvc mockMvc;
...
}

Related

Request-scoped beans not working in Spring tests with Cucumber

I have an application based on Spring 4.3.28 (i.e. not Spring Boot!) and I want to migrate my integration tests to Cucumber.
I’ve followed this tutorial and adapted it to plain Spring.
The tests I’ve written so far are working fine (Spring context is initialized etc.), but as soon as there are request-scoped beans involved, they stop working:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
I’ve created a small sample project
that tries to reproduce the problem.
There is one context configuration class called AppConfig:
#Configuration
public class AppConfig {
#Bean
#Scope("request“) // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
#Bean("dependency")
#Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
The ExampleService is request-scoped, and gets one request-scoped bean injected by #Autowired:
public class ExampleService {
#Autowired
#Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
For the tests, I have one Spring-annotated superclass:
#ContextConfiguration(classes = AppConfig.class)
#CucumberContextConfiguration
#WebAppConfiguration
public class TestBase {
#Autowired
public ExampleService underTest;
}
There’s also a plain Spring test that runs just fine:
#RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
#Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
The Cucumber test is executed by this test class stub:
#RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
The actual cucumber step definitions are here:
public class CucumberStepDefinitions extends TestBase {
private String result;
#When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
#Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
The .feature file for Cucumber is in the src/test/resources directory under the same package name as the step definitions class:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
Usually when I encountered the „no thread-bound request found“ error, it was because the #WebAppConfiguration annotation was missing, or when I tried to inject a request-scoped bean into a non-request scoped bean. But that’s not the case here.
What am I doing wrong?
I was able to figure out how to resolve it; the updated code is in the github repository linked in the question.
When using the SpringRunner, the request context is initialized in a ServletTestExecutionListener that is implicitly added to the list of TestExecutionListeners for the test.
The initialization happens in the beforeTestMethod() method of that listener.
However, as #M.P.Korsanje correctly remarked in the comments (thanks!), Cucumber doesn't have test methods, so beforeTestMethod() is never executed.
My solution was to add a custom subclass of ServletTestExecutionListener as a TestExecutionListener that delegates the beforeTestClass() call to the beforeTestMethod():
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
And in ExampleServiceCucumberTest:
#ContextConfiguration(classes = {AppConfig.class})
#CucumberContextConfiguration
#WebAppConfiguration
#TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// extend the Spring class to get the default TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
#Autowired
public ExampleService underTest;
}

Getting "At least one JPA metamodel must be present" with #WebMvcTest

I'm fairly new to Spring, trying to do some basic integration tests for a #Controller.
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
#MockBean
private DemoService demoService;
#Test
public void index_shouldBeSuccessful() throws Exception {
mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
}
}
but I'm getting
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Unlike most people posting this error, I don't want to use JPA for this. Am I trying to use #WebMvcTest incorrectly? How can I track down the Spring magic that's inviting JPA to this party?
Remove any #EnableJpaRepositories or #EntityScan from your SpringBootApplication class instead do this:
package com.tdk;
#SpringBootApplication
#Import({ApplicationConfig.class })
public class TdkApplication {
public static void main(String[] args) {
SpringApplication.run(TdkApplication.class, args);
}
}
And put it in a separate config class:
package com.tdk.config;
#Configuration
#EnableJpaRepositories(basePackages = "com.tdk.repositories")
#EntityScan(basePackages = "com.tdk.domain")
#EnableTransactionManagement
public class ApplicationConfig {
}
And here the tests:
#RunWith(SpringRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
}
I had the same problem. #WebMvcTest looks for a class annotated with #SpringBootApplication (in the same directory or higher up in your app structure if it doesn't find one). You can read how this works # https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests.
If your class annotated with #SpringBootApplication also has #EntityScan /#EnableJpaRepositories this error occurs. Because you have these annotations with #SpringBootApplication and you are mocking the service ( so actually not using any JPA ). I found a workaround which may not be the prettiest, but works for me.
Place this class in your test directory ( the root ). #WebMvcTest will find this class before your actual Application class. In this class you don't have to add #EnableJpaRepositories/#EntityScan.
#SpringBootApplication(scanBasePackageClasses = {
xxx.service.PackageMarker.class,
xxx.web.PackageMarker.class
})
public class Application {
}
And your test will look the same.
#RunWith(SpringRunner.class)
#WebMvcTest
#WithMockUser
public class ControllerIT {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
public void testName() throws Exception {
// when(service.xxx(any(xxx.class))).thenReturn(xxx);
// mockMvc.perform(post("/api/xxx")...
// some assertions
}
}
Hope this helps!
Alternatively, you can define a custom configuration class inside your test case, including only the controller (plus its dependencies), to force Spring to use this context.
Please note, you'll still have access to MockMvc and other goodness in your test case, if it's WebMvcTest annotated.
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
#MockBean
private DemoService demoService;
#Test
public void index_shouldBeSuccessful() throws Exception {
mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
}
#Configuration
#ComponentScan(basePackageClasses = { DemoController.class })
public static class TestConf {}
Add #MockBean(JpaMetamodelMappingContext.class) to above of class DemoControllerIntegrationTests:
#RunWith(SpringRunner.class)
#WebMvcTest(DemoController.class)
#MockBean(JpaMetamodelMappingContext.class)
public class DemoControllerIntegrationTests {
...
}
Because you have not used a database in your test, Spring throws this exception. By mocking JpaMetamodelMappingContext class you will bypass the needed metamodel.
If anyone uses Spring boot and don't want to remove #EntityScan and #EnableJpaRepositories you can remove #WebMvcTest annotation from your test class and add the following instead:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class DemoControllerIntegrationTests {
#Autowired
private MockMvc mvc;
//...
}
and you will be able to autowire MockMvc and use it.

Spring JUnit Test not loading full Application Context

Hi I am trying to so spring junit test cases... and I require my full application context to be loaded. However the junit test does not initialize the full application context.
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class MongoDbRepositoryTest {
#Value("${spring.datasource.url}")
private String databaseUrl;
#Inject
private ApplicationContext appContext;
#Test
public void testCRUD() {
System.out.println("spring.datasource.url:" + databaseUrl);
showBeansIntialised();
assertEquals(1, 1);
}
private void showBeansIntialised() {
System.out.println("BEEEAAANSSSS");
for (String beanName : appContext.getBeanDefinitionNames()) {
System.out.println(beanName);
}
}
Output:
spring.datasource.url:${spring.datasource.url}
BEEEAAANSSSS
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.internalPersistenceAnnotationProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
Main Application Class Annotations:
#ComponentScan(basePackages = "com.test")
#EnableAutoConfiguration(exclude = { MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class })
#EnableMongoRepositories("com.test.repository.mongodb")
#EnableJpaRepositories("com.test.repository.jpa")
#Profile(Constants.SPRING_PROFILE_DEVELOPMENT)
public class Application { ...
Hence it should scan all the spring bean in the package com.test and also load them into the applicationcontext for the Junit testcase. But from the output of the beans initalised it doesnt seem to be doing this.
You need to annotate your test class with #ActiveProfiles as follows; otherwise, your Application configuration class will always be disabled. That's why you currently do not see any of your own beans listed in the ApplicationContext.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#ActiveProfiles(Constants.SPRING_PROFILE_DEVELOPMENT)
public class MongoDbRepositoryTest { /* ... */ }
In addition, Application should be annotated with #Configuration as was mentioned by someone else.
Are you maybe missing an #Configuration annotation on your Application class?
Adding #ActiveProfile in each test class you cant scale better added this in VM options
-Dspring.profiles.active=test

Spring Boot Configuration

I have 2 applications, one using Spring inside a web-app, and another local application using Spring Boot. These 2 share a configuration class between them.
I cannot figure out how to configure the local application correctly.
Here is the basic layout of classes I am using:
The main class
#EnableAutoConfiguration
class MainClass{
#Autowired
private static MyComponent component;
public static void main(String args[]){
// code
SpringApplication.run(MyConfiguration.class, args);
component.start();
}
}
The configuration
#Configuration
#EnableConfigurationProperties
#PropertySource("classpath:/path/to/queue.properties")
public class MyConfiguration {
#Autowired
public static Environment env;
// more beans
#Bean
#Qualifier("qualifier1")
public static String getName(){ //made String and simple to match the Component's Autowired
return env.getProperty("property.name");
}
}
The component
#Component
#EnableAutoConfiguration
public class MyComponent extends Thread {
#Autowired
#Qualifier("qualifier1")
private String template; // this isn't actually String, but a springAMQP class. Should have the same effect though.
#Override
public void run(){
//code
template.charAt(0); // just something that fails if it was not autowired
//code
}
}
If .run is given MyConfiguration.class i get a null pointer within the autowired Environment in MyConfiguration. If it is given MainClass.class the autowired MyComponent is still null.
As for some layout restrictions,
the Main Class and MyComponent only exist in the local application. The Configuration is in a shared package between the local application and the web application. This prevents me from simply creating the Bean with MyComponent in the Configuration due to dependencies.
If I remove the MyComponent from the MainClass and add the following configuration within the Local application:
#Configuration
public class MyLocalConfiguration extends MyConfiguration {
private MyComponent listener;
#Bean
public MyComponent getListener(){
if(listener == null){
listener = new MyComponent();
listener.start();
}
return listener;
}
}
I still have the issue of the Environment being null in MyConfiguration, preventing the other beans from being set up.
There are 2 problems with your configuration
#Autowired will only work for Spring Managed beans, your MainClass isn't spring managed so no autowiring is going to happen
#Autowired will not work on static fields

XML-less configuration for spring

I have the following configuration bean for a non web app
#Configuration
public class MyBeans {
#Bean
#Scope(value="prototype")
MyObject myObject() {
return new MyObjectImpl();
}
}
On the other side I have my class
public class MyCommand implements Command {
#Autowired
private MyObject myObject;
[...]
}
How can I make myCommand be autowired with the configuration in MyBeans without using XML so I can inject mocks in my other test classes?
Thanks a lot in advance.
With XML-based configuration you'd use the ContextConfiguration annotation. However, the ContextConfiguration annotation doesn't appear to work with Java Config. That means that you have to fall back on configuring your application context in the test initialization.
Assuming JUnit4:
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest{
private ApplicationContext applicationContext;
#Before
public void init(){
this.applicationContext =
new AnnotationConfigApplicationContext(MyBeans.class);
//not necessary if MyBeans defines a bean for MyCommand
//necessary if you need MyCommand - must be annotated #Component
this.applicationContext.scan("package.where.mycommand.is.located");
this.applicationContext.refresh();
//get any beans you need for your tests here
//and set them to private fields
}
#Test
public void fooTest(){
assertTrue(true);
}
}

Categories

Resources