How to test custom WebApplicationInitializer in Spring - spring-boot

I moved web.xml to Java annotation configuration
My custom code is
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setDisplayName("app-name");
context.register(Config.class);
container.addListener(new RequestContextListener());
container.addListener(new ContextLoaderListener(context));
Dynamic dispatcher = container.addServlet("app-name", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/app-name-rest/*");
}
I tried the solution https://stackoverflow.com/a/25210356/6700081 but the line coverage is still 0%

I had the same problem and solved in this way:
public class WebInitializerTest {
#Mock
private MockServletContext mockServletContext;
#Mock
private ServletRegistration.Dynamic mockServletRegistration;
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testOnStartup() {
WebInitializer webInitializer = new WebInitializer();
when(mockServletContext.addServlet(ArgumentMatchers.eq("app-name"), any(Servlet.class))).thenReturn(mockServletRegistration);
webInitializer.onStartup(mockServletContext);
verify(mockServletContext, times(1)).addListener(any(RequestContextListener.class));
// other asserts..
}}
Note: I use Junit5 (#BeforeEach)

Related

Testing multiple Spring webApplicationContext in same test

I have 1 application that was a single module with /intranet/** and /internet/** endpoints. Now I am splitting the application in two modules: intranet and internet. Each module will create a war so now I have two applications.
My problem is that I have multiple junit tests that invoke /intranet/** and /internet/** endpoints.
I can't figure how to configure the testing context in a way that I can invoke both application contexts.
In the intranet module I have the following configurations:
#Configuration
#ComponentScan(...)
#EnableWebMvc
#EnableTransactionManagement
public class MvcConfigIntra extends WebMvcConfigurerAdapter {
...
)
public class IntraWebInitializer implements WebApplicationInitializer{
#Override
public final void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfigIntra.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
AnnotationConfigWebApplicationContext webAppContext = new AnnotationConfigWebApplicationContext();
webAppContext.setParent(rootContext);
webAppContext.register(MvcConfigIntra.class);
ServletRegistration.Dynamic dispatcherWebApp = servletContext.addServlet("dispatcherIntranet", new DispatcherServlet(webAppContext));
dispatcherWebApp.addMapping("/*");
}
}
In the internet module it is the same thing:
#Configuration
#ComponentScan(...)
#EnableWebMvc
#EnableTransactionManagement
public class MvcConfigInter extends WebMvcConfigurerAdapter {
...
)
public class WebInitializer implements WebApplicationInitializer {
#Override
public final void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
AnnotationConfigWebApplicationContext webAppContext = new AnnotationConfigWebApplicationContext();
webAppContext.setParent(rootContext);
webAppContext.register(MvcConfigInter.class);
ServletRegistration.Dynamic dispatcherWebApp = servletContext.addServlet("dispatcherInternet", new DispatcherServlet(webAppContext));
dispatcherWebApp.addMapping("/*");
}
}
Now for tests:
#Configuration
#Import({
...
})
#Profile("test")
public class IntraTestConfig extends MvcConfigIntra {
}
#Configuration
#Import({
...
})
#Profile("test")
public class InterTestConfig extends MvcConfigInter {
}
A test inside intranet module:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(
classes = {
IntraTestConfig.class,
InterTestConfig.class
},
loader = AnnotationConfigWebContextLoader.class
)
#ActiveProfiles({"test"})
public class TestInternetAndIntranet {
private MockMvc mockMvcIntranet;
private MockMvc mockMvcInternet;
#Before
public void init() throws Exception {
this.mockMvcIntranet = MockMvcBuilders.webAppContextSetup(this.ctx).build();
/*HOW TO GET the right AppContext ?!*/
//this.mockMvcInternet = MockMvcBuilders.webAppContextSetup(this.ctx).build();
}
#Test
public final void test1() {
//This works
mockMvc.perform(post("/intranet/submeter")
//This DOES NOT work
mockMvc.perform(post("/internet/submeter")
}
}
The error is:
javax.servlet.ServletException: Could not resolve view with name 'internet/500' in servlet with name ''
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1262)
at org.springframework.test.web.servlet.TestDispatcherServlet.render(TestDispatcherServlet.java:105)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:770)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155)
One thing I don't know is how to get the AppContext for mockMvcInternet.
Is this possible? What's missing in this test configuration?
I could not use two application contexts.
So, I changed the solution.
I made a new module old-tests to put all the tests that invoke /intranet/** and /internet/** endpoints.
In this module I have a Spring Application Context with all endpoints.

How to do unit test for spring boot auto configuration

In a spring boot auto configuration project there are two emailSender child classes: MockEmailSender and TextEmailSender. And in auto configuration only one mailSender should be created:
#Bean
#ConditionalOnMissingBean(MailSender.class)
#ConditionalOnProperty(name="spring.mail.host", havingValue="foo", matchIfMissing=true)
public MailSender mockMailSender() {
log.info("Configuring MockMailSender");
return new MockMailSender();
}
#Bean
#ConditionalOnMissingBean(MailSender.class)
#ConditionalOnProperty("spring.mail.host")
public MailSender smtpMailSender(JavaMailSender javaMailSender) {
log.info("Configuring SmtpMailSender");
return new SmtpMailSender(javaMailSender);
}
following is my unit test code:
#SpringBootApplication
public class LemonTest implements ApplicationContextAware{
private ApplicationContext context;
public static void main(String[] args){
SpringApplication.run(LemonTest.class, args);
System.out.println("haha");
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class InitTest {
#Autowired
private MailSender mailSender;
#Test
public void test(){
assertNotNull(mailSender);
}
}
And the properties are
spring.mail.host=foo
spring.mail.port=587
spring.mail.username=alert1
spring.mail.password=123456
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
Based on my auto configuration only MockEmailSender should be initialized,
but both emailSender bean are created, so multiple bean error is thrown when running the unit test. I guess my configuration settings are not loaded by the test.
So how to include the auto configuration in the test? what would be the best practices to test auto configuration?
I've always created multiple tests using separate "profiles" to control what is loaded/set, setting the active profile on the test.
#ActiveProfiles({ "test", "multipleemail" })
Then your test would ensure the expected result (multiple email providers in context, etc).
Properties can be imported if you need separate ones. I store one in src/test specifically for my unit tests.
#PropertySource({ "classpath:sparky.properties" })
I finally solve it. Just add #Import(LemonAutoConfiguration.class) to the application.
#SpringBootApplication
#Import(LemonAutoConfiguration.class)
public class LemonTest implements ApplicationContextAware{
private ApplicationContext context;
public static void main(String[] args){
SpringApplication.run(LemonTest.class, args);
System.out.println("haha");
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}

Spring 4, Thymeleaf, Security: No WebApplicationContext found: no ContextLoaderListener registered [duplicate]

This question already has answers here:
No WebApplicationContext found: no ContextLoaderListener registered?
(2 answers)
Closed 5 years ago.
It drives me mad... All the things that work so fare, I had to painful search together from more are less good examples and now I got stuck with something that should work out of the box.
Important: I use Java configuration, so there is no XML at all.
An abstract from the build.gradle
dependencies {
compile 'org.springframework:spring-webmvc:4.2.3.RELEASE',
'org.thymeleaf:thymeleaf-spring4:2.1.4.RELEASE',
'com.fasterxml.jackson.core:jackson-databind:2.6.3',
'com.google.guava:guava:18.0',
'org.springframework.security:spring-security-config:4.0.3.RELEASE',
'org.springframework.security:spring-security-web:4.0.3.RELEASE',
'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE'
}
The interesting part from the WebMvcConfigurationAdapter
#Configuration
#ComponentScan("de.mypackage")
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Bean
IDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
#Bean
#Inject
SpringTemplateEngine templateEngine(final CustomTemplateResolver customTemplateResolver,
final ServletContextTemplateResolver servletContextTemplateResolver,
final IDialect springSecurityDialect) {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(customTemplateResolver);
templateEngine.addTemplateResolver(servletContextTemplateResolver);
templateEngine.addDialect(springSecurityDialect);
return templateEngine;
}
}
The obligatory WebApplicationInitializer
public final class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(final ServletContext servletContext) {
log.debug("start up {}", servletContext);
try (
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext()) {
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
}
My version of a WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
private static final String LOGIN_URL = "/#login";
#Inject UserDetailsService userService;
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.anonymous().principal(getAnonymousUser()).authorities("ROLE_ANOYMOUS");
final String adminAccess = String.format("hasAnyRole('ROLE_%s', 'ROLE_%s')",
Role.SYSTEM_ADMINISTRATOR, Role.USER_ADMINISTRATOR);
log.debug("Configure admin access: {}", adminAccess);
http.authorizeRequests().antMatchers("/admin/**").access(adminAccess).and().formLogin()
.loginPage(LOGIN_URL);
final String systemAdminAccess = String.format("hasRole('ROLE_%s')", Role.SYSTEM_ADMINISTRATOR);
log.debug("Configure system admin access: {}", systemAdminAccess);
http.authorizeRequests().antMatchers("/rest/templates/**").access(systemAdminAccess).and()
.formLogin().loginPage(LOGIN_URL);
}
Principal getAnonymousUser() {
final Principal principal = () -> "guest";
return principal;
}
#Bean
AuthenticationManager getAuthenticationManager() throws Exception {
log.debug("Authentication manager configured");
return authenticationManager();
}
#Bean
PasswordEncoder passwordEncoder() {
final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
return passwordEncoder;
}
}
Then it turns out, that this template was not working
<meta th:name="${_csrf.parameterName}" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
The solution was to create an empty class of type AbstractSecurityWebApplicationInitializer
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {}
Now this was also working.
But, as always, only one step to the next Problem.
This is working fine:
<li th:if="${#authentication.name == 'guest'}">Anmelden</li>
But I prefer to check against a role, not a user and this leads to an error:
<li th:if="${#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')}">Anmelden</li>
Abstract from Trace:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')" (main/header:11)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
caused by
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')" (main/header:11)
org.thymeleaf.spring4.expression.SpelVariableExpressionEvaluator.evaluate(SpelVariableExpressionEvaluator.java:161)
causef by
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83)
I'm out of ideas so fare...
Again it was only a small piece missing...
public final class WebAppInitializer implements WebApplicationInitializer {
private static final Logger log = LoggerFactory.getLogger(WebAppInitializer.class);
#Override
public void onStartup(final ServletContext servletContext) {
log.debug("start up {}", servletContext);
try (
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext()) {
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
servletContext.addListener(new ContextLoaderListener(ctx));
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
}
Look at the servletContext.addListener

how to add filters to servlet when using #enablewebmvc annotation in spring 4?

Here is the current configuration
public class WebAppConfig implements WebApplicationInitializer {
private static final String CHARACTER_ENCODING_FILTER_ENCODING = "UTF-8";
private static final String CHARACTER_ENCODING_FILTER_NAME = "characterEncoding";
private static final String CHARACTER_ENCODING_FILTER_URL_PATTERN = "/*";
private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
private static final String DISPATCHER_SERVLET_MAPPING = "/";
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(ExampleApplicationContext.class);
configureDispatcherServlet(servletContext, rootContext);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
configureCharacterEncodingFilter(servletContext, dispatcherTypes);
servletContext.addListener(new ContextLoaderListener(rootContext));
}
private void configureDispatcherServlet(ServletContext servletContext, WebApplicationContext rootContext) {
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
DISPATCHER_SERVLET_NAME,
new DispatcherServlet(rootContext)
);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
}
private void configureCharacterEncodingFilter(ServletContext servletContext, EnumSet<DispatcherType> dispatcherTypes) {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding(CHARACTER_ENCODING_FILTER_ENCODING);
characterEncodingFilter.setForceEncoding(true);
FilterRegistration.Dynamic characterEncoding = servletContext.addFilter(CHARACTER_ENCODING_FILTER_NAME, characterEncodingFilter);
characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, CHARACTER_ENCODING_FILTER_URL_PATTERN);
}
}
Now I want to use #EnableWebMvc
#EnableWebMvc
#ComponentScan(basePackages = { "com.example.mvc.base.controller" })
public class WebAppConfig extends WebMvcConfigurerAdapter {
}
how would I add filters and Listeners to servletContext which I did using WebApplicationInitializer?
You still continue adding filters and listeners thorough your WebApplicationInitializer. This class methods starts up the web application with a Servlet registered with required contexts and beans.
In This class a DispatcherServlet is created containing programmatically configured AnnotationConfigWebApplicationContext.The dispatcher is also mapped to .html. 
WebAppConfig class enables WebMVC based on annotation and scans the base packages for the annotated resources. These are further registered by the web application context of the default servlet which was initialized by WebApplicationInitializer.

can not start spring mvc 4 application without web.xml

I am trying to deploy spring mvc 4 web application without web.xml file using #Configuration annotation only.
I have
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
"DispatcherServlet", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("*.html");
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("ge.dm.icmc.config.WebConfig");
return context;
}
}
and my WebConfig.java class looks like :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages="ge.dm.icmc")
public class WebConfig{
}
But when I try to start the application, I see in log :
14:49:12.275 [localhost-startStop-1] DEBUG o.s.w.c.s.AnnotationConfigWebApplicationContext - Could not load class for config location [] - trying package scan. java.lang.ClassNotFoundException:
If I try to add web.xml file, then it is started normally.
You are using the method setConfigLocation which, in this case, is wrong. You should use the register method instead.
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(ge.dm.icmc.config.WebConfig.class);
return context;
}
However instead of implementing the WebApplicationInitializer I strongly suggest using one of the convenience classes of Spring for this. In your case the AbstractAnnotationConfigDispatcherServletInitializer would come in handy.
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() { return null;}
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class};
}
protected String[] getServletMappings() {
return new String[] {"*.html"};
}
}

Resources