We have a URL that has to end with (customer requirement) something like /test.jsp.
Now that we're finally running Spring Boot (2.1.1.RELEASE), we would like to ditch JSP and use some templating engine, in this case Mustache. I have a controller mapping like this:
#Controller
#RequestMapping("/")
public class TestController {
#GetMapping(path = "/test.jsp")
public ModelAndView test(...) {...}
}
This just doesn't work. I have
spring.mvc.pathmatch.use-suffix-pattern=true
In our application.properties, anything spring.mvc.view-related is commented out, when I add another mapping with just /test, it works for /test. Funny thing is I have managed to get the same exact thing working when using Spring MVC and Thymeleaf but I can't seem to find the difference.
Additionally, I added a test for this like so:
#ExtendWith(SpringExtension.class)
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
class TestTest {
#Autowired
private MockMvc mockMvc;
#Test
void test() throws Exception {
final MockHttpServletRequestBuilder testRequestBuilder = MockMvcRequestBuilders.get("/test.jsp");
MvcResult responseResult = mockMvc.perform(testRequestBuilder).andReturn();
response = responseResult.getResponse();
assertThat(response.getStatus(), equalTo(HttpStatus.OK.value()));
}
}
This works fine, the content of the response is also exactly what I want. The test profile is the same as the one when using mvn spring-boot:run for the time being.
Anyone got an idea on how to get this working? Thanks!
The dot is probably being truncated. or escaped.
You can probably do something like this:
#GetMapping("/{pageName:.+}")
public void test(
#PathVariable("pageName") String pageName) {
if(pageName.equals("test.jsp")) {
//...
}
I know you dont exactly want a variable, but just throwing an idea
I finally solved it - I had everything working including test.html, test.xml, test.wasd and test. So I figured it couldn't be Spring by itself. After some debugging in various Tomcat classes I found the culprit: JspServlet was somehow present in the classpath and being automatically configured, originating from
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
Removing the dependency led to test.jsp being recognized correctly.
Related
I'm trying to write my first Spring MVC test but I just cannot get Spring Boot to inject the MockMvc dependency into my test class. Here is my class:
#WebMvcTest
public class WhyWontThisWorkTest {
private static final String myUri = "uri";
private static final String jsonFileName = "myRequestBody.json";
#Autowired
private MockMvc mockMvc;
#Test
public void iMustBeMissingSomething() throws Exception {
byte[] jsonFile = Files.readAllBytes(Paths.get("src/test/resources/" + jsonFileName));
mockMvc.perform(
MockMvcRequestBuilders.post(myUri)
.content(jsonFile)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().is2xxSuccessful());
}
}
I've checked with IntelliJ's debugger and can confirm that mockMvc itself is null. Thus, all the Exception message tells me is "java.lang.NullPointerException".
I've already tried adding more general Spring Boot annotations for test classes like "#SpringBootTest" or "#RunWith(SpringRunner.class)" in case it has something to do with initializing Spring but no luck.
Strange, provided that you have also tried with #RunWith(SpringRunner.class) and #SpringBootTest. Have you also tried with the #AutoConfigureMockMvc annotation? The sample below is working fine.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class HelloControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void getHello() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("Hello World of Spring Boot")));
}
}
complete sample here
It may also be worthwhile to consider the following comments regarding the usage of the #WebMvcTest and #AutoConfigureMockMvc annotations as detailed in Spring's documentation
By default, tests annotated with #WebMvcTest will also auto-configure Spring Security and MockMvc (include support for HtmlUnit WebClient and Selenium WebDriver). For more fine-grained control of MockMVC the #AutoConfigureMockMvc annotation can be used.
Typically #WebMvcTest is used in combination with #MockBean or #Import to create any collaborators required by your #Controller beans.
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.
When using JUnit 4, this annotation should be used in combination with #RunWith(SpringRunner.class).
I faced the same issue. It turned out that MockMvc was not injected due to incompatible #Test annotation. The import was org.junit.Test, but changing it to org.junit.jupiter.api.Test solved the issue.
Accepted answer works, however I solved the issue also without importing #RunWith(SpringRunner.class).
In my case I had imported org.junit.Test instead of the newer org.junit.jupiter.api.Test.
If you are using Maven you can avoid to make this mistake excluding junit-vintage-engine from spring-boot-starter-test dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
check if you have the latest or at least updated maven-surefire-plugin.
It helped me to solve the issue
My application is on Spring boot 1.5.1
I have looked up all common problems related to caching not working in spring boot (calling a cacheable method from the same class, etc) and I still can't seem to pinpoint why my method is not cacheing. I am just trying to use the simple cache built into Spring (ie the concurrent hashmap).
Setup: in pom.xml I added this
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
My configuration class is the following:
#SpringBootApplication
#Configuration
#EnableCaching
#EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I have a controller class that has the following method to get a list of versions.
#RequestMapping(value = "/getVersionList", method = RequestMethod.GET)
public JSONObject getVersionList() {
JSONObject retJSON = new JSONObject();
List<String> versions = fetchVersionService.getVersionList();
retJSON.put("versions", versions);
return retJSON;
}
And the FetchVersionService class where I have the cacheable method to cache this versions list
#Cacheable("versions")
public List<String> getVersionList() {
System.out.println("If there is something in the cache, it should not hit here.");
return randomService.getVersions(); //another service class that gets versions from a remote location, which takes a long time
}
Whenever I make multiple GET calls to this function, it always goes inside the function even though I only expect to it execute the function once. For some reason it isn't cacheing the results.
Any ideas where I went wrong with my setup? Thanks a lot.
In the comments under this question #irfanhasan mentions that they had imported the wrong package but there was no certain response.
I faced the same problem. I tried to use #Cachable annotation but it wasn't work. I found that it was imported as:
import springfox.documentation.annotations.Cacheable
Instead of:
import org.springframework.cache.annotation.Cacheable;
Look carefully for your IDE imports.
In the comments under this question, #irfanhasan mentions that they had imported the wrong package and #xetra11 replied asking which package was wrong which there was no reply to. I am not #irfanhasan but it might have been the following mixup: "You can also use the standard JSR-107 (JCache) annotations (such as #CacheResult) transparently. However, we strongly advise you to not mix and match the Spring Cache and JCache annotations."
I'm testing a Spring MVC #RestController which in turn makes a call to an external REST service. I use MockMvc to simulate the spring environment but I expect my controller to make a real call to the external service. Testing the RestController manually works fine (with Postman etc.).
I found that if I setup the test in a particular way I get a completely empty response (except the status code):
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = AnywhereController.class)
public class AnywhereControllerTest{
#Autowired
private AnywhereController ac;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testGetLocations() throws Exception {
...
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/anywhere/locations").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(containsString("locations")))
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON));
.andReturn();
}
The test fails because the content and headers are empty. Then I tried adding this to the test class:
#Configuration
#EnableWebMvc
public static class TestConfiguration{
#Bean
public AnywhereController anywhereController(){
return new AnywhereController();
}
}
and additionally I changed the ContextConfiguration annotation (although I'd like to know what this actually does):
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class AnywhereControllerTest{...}
Now suddenly all the checks succeed and when printing the content body I'm getting all the content.
What is happening here? What is the difference between these two approaches?
Someone in the comments mentioned #EnableWebMvc and it turned out this was the right lead.
I wasn't using #EnableWebMvc and therefore
If you don't use this annotation you might not initially notice any difference but things like content-type and accept header, generally content negotiation won't work. Source
My knowledge about the inner workings of the framework is limited but it a simple warning during startup could potentially save many hours of debugging. Chances are high that when people use #Configuration and/or #RestController that they also want to use #EnableWebMvc (or the xml version of it).
Making things even worse, Spring Boot for example adds this annotation automatically, which is why many tutorials on the internet (also the official ones) don't mention #EnableWebMvc.
recently I changed my spring boot properties to define a management port.
In doing so, my unit tests started to fail :(
I wrote a unit test that tested the /metrics endpoint as follows:
#RunWith (SpringRunner.class)
#DirtiesContext
#SpringBootTest
public class MetricsTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
/**
* Called before each test.
*/
#Before
public void setUp() {
this.context.getBean(MetricsEndpoint.class).setEnabled(true);
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
/**
* Test for home page.
*
* #throws Exception On failure.
*/
#Test
public void home()
throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/metrics"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Previously this was passing. After adding:
management.port=9001
The tests started failing with:
home Failed: java.lang.AssertionError: Status expected: <200> but was: <404>
I tried changing the #SpringBootTest annotation with:
#SpringBootTest (properties = {"management.port=<server.port>"})
Where is the number used for the server.port. This didn't seem to make any difference.
So then changed the management.port value in the property file to be the same as the server.port. Same result.
The only way to get the test to work is remove the management.port from the property file.
Any suggestions/thoughts ?
Thanks
For Spring Boot 2.x the integration tests configuration could be simplified.
For example simple custom heartbeat endpoint
#Component
#Endpoint(id = "heartbeat")
public class HeartbeatEndpoint {
#ReadOperation
public String heartbeat() {
return "";
}
}
Where integration test for this endpoint
#SpringBootTest(
classes = HeartbeatEndpointTest.Config.class,
properties = {
"management.endpoint.heartbeat.enabled=true",
"management.endpoints.web.exposure.include=heartbeat"
})
#AutoConfigureMockMvc
#EnableAutoConfiguration
class HeartbeatEndpointTest {
private static final String ENDPOINT_PATH = "/actuator/heartbeat";
#Autowired
private MockMvc mockMvc;
#Test
void testHeartbeat() throws Exception {
mockMvc
.perform(get(ENDPOINT_PATH))
.andExpect(status().isOk())
.andExpect(content().string(""));
}
#Configuration
#Import(ProcessorTestConfig.class)
static class Config {
#Bean
public HeartbeatEndpoint heartbeatEndpoint() {
return new HeartbeatEndpoint();
}
}
}
For Spring boot test we need to specify the port it needs to connect to.
By default, it connects to server.port which in case of actuators is different.
This can be done by
#SpringBootTest(properties = "server.port=8090")
in application.properties we specify the management port as below
...
management.server.port=8090
...
Did you try adding the following annotation to your test class?
#TestPropertySource(properties = {"management.port=0"})
Check the following link for reference.
Isn't there an error in the property name?
Shouldn't be
#TestPropertySource(properties = {"management.server.port=..."}) instead of #TestPropertySource(properties = {"management.port=.."})
The guide stated that this can be achieved with #AutoConfigureMetrics.
And I moved with this.
Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using #SpringBootTest.
If you need to export metrics to a different backend as part of an integration test, annotate it with #AutoConfigureMetrics.
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.metrics
Had the same issue, you just have to make the management.port null by adding this in your application-test.properties (set it to empty value)
management.port=
Make sure you use the test profile in your JUnit by annotating the class with
#ActiveProfiles("test")
Try using
#SpringBootTest(properties = {"management.port="})
Properties defined in the #SpringBootTest annotation have a higher precedence than those in application properties. "management.port=" will "unset" the management.port property.
This way you don't have to worry about configuring the port in your tests.
I was facing the same issue and tried several things but this is how I was able to solve mine without making any change in the application.yaml
Sample actuator endpoint
#Component
#RestControllerEndpoint(id = "endpoint")
public class SampleEndpoint
{
#GetMapping
public String sampleEndpoint(){
return ""
}
}
Unit test case
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {SampleEndpointTest.Config.class},
properties = {"management.server.port="}
)
#AutoConfigureMockMvc
public class SampleEndpointTest
{
#Autowired
private MockMvc mockMvc;
#SpringBootApplication(scanBasePackageClasses = {SampleEndpoint.class})
public static class Config
{
}
#Test
public void testSampleEndpoint() throws Exception
{
mockMvc.perform(
MockMvcRequestBuilders.get("/actuator/enpoint").accept(APPLICATION_JSON)
).andExpect(status().isOk());
}
Since now info endpoint must be enabled manually make sure the SpringBootTest tag includes this in properties, like this:
#SpringBootTest(
properties = {
"management.info.env.enabled=true" ,
"management.endpoints.web.exposure.include=info, health"
})
I had this problem recently, and as none of the above answers made any sense to me, I decided to do a bit more reading. In my case, I had already defined both server.port and management.server.port as 8091 in my test application-test.yaml file, and could not understand why my test was getting a connection refused error message.
It turns out that instead of using the annotation #SpringBootTest() I needed to use #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) - which causes the port numbers in the yaml file to be used. This is briefly discussed in the manual. Quoting the relevant section:
DEFINED_PORT — Loads an EmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from your application.properties or on the default port 8080).
It seems in SpringBootTest the default is to avoid starting a real servlet environment, and if no WebEnvironment is explicitly specified then SpringBootTest.WebEnvironment.MOCK is used as a default.
After a long search: There is this nice Springboot annotation called #LocalManagementPort!
It works similar to #LocalServerPort but for actuator endpoins.
An example config would look as follows
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MetricsIT {
#Autowired
RestTemplateBuilder restTemplateBuilder;
#LocalManagementPort
int managementPort;
#Test
public void testMetrics(){
ResponseEntity<String> response = restTemplateBuilder
.rootUri("http://localhost:" + managementPort + "/actuator")
.build().exchange("/metrics", HttpMethod.GET, new HttpEntity<>(null), String.class);
}
}
I am using Spring Boot 1.3, Spring 4.2 and Spring Security 4.0. I am running integration tests using MockMvc, for example:
mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
In my tests I am simulating a user login like this:
CurrentUser principal = new CurrentUser(user);
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "dummypassword",
principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
This works fine for my methods that are annotated with #PreAuthorize, for example when calling a method like this from a test:
#PreAuthorize("#permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()
the principle, my CurrentUser object, is non-null in PermissionsService#canDoThisThing.
I have a class annotated with #ControllerAdvice that adds the currently logged-in user to the model so it can be accessed in every view:
#ControllerAdvice
public class CurrentUserControllerAdvice {
#ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
if (authentication == null) {
return null;
}
return (CurrentUser) authentication.getPrincipal();
}
}
This works fine when running the application, however (and this is my problem) - when running my tests the authentication parameter passed in to the getCurrentUser method above is always null. This means any references to the currentUser attribute in my view templates cause errors, so those tests fail.
I know I could get round this by retrieving the principle like this:
authentication = SecurityContextHolder.getContext().getAuthentication();
but I would rather not change my main code just so the tests work.
Setting the SecurityContextHolder does not work when using Spring Security with MockMvc. The reason is that Spring Security's SecurityContextPersistenceFilter attempts to resolve the SecurityContext from the HttpServletRequest. By default this is done using HttpSessionSecurityContextRepository by retrieving at the HttpSession attribute named SPRING_SECURITY_CONTEXT. The attribute name is defined by the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY. Whatever SecurityContext is found in the HttpSession will then be set on the SecurityContextHolder which overrides the value you previously set.
Manually Solving the Issue
The fix that involves the least amount of change is to set the SecurityContext in the HttpSession. You can do this using something like this:
MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andExpect(status().isOk())
.etc;
The key is to ensure that you set the HttpSession attribute named SPRING_SECURITY_CONTEXT to the SecurityContext. In our example, we leverage the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY to define the attribute name.
Spring Security Test
Spring Security 4.0 has officially added test support. This is by far the easiest and most flexible way to test your application with Spring Security.
Add spring-security-test
Since you are using Spring Boot, the easiest way to ensure you have this dependency is to include spring-security-test in your Maven pom. Spring Boot manages the version, so there is no need to specify a version.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Naturally, Gradle inclusion would be very similar.
Setting up MockMvc
In order to integrate with MockMvc there are some steps you must perform outlined in the reference.
The first step is to ensure you use #RunWith(SpringJUnit4ClassRunner.class). This should come as no surprise since this is a standard step when testing with Spring applications.
The next step is to ensure you build your MockMvc instance using SecurityMockMvcConfigurers.springSecurity(). For example:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class MyTests {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // sets up Spring Security with MockMvc
.build();
}
...
Running as a User
Now you can easily run with a specific user. There are two ways of accomplishing this with MockMvc.
Using a RequestPostProcessor
The first option is using a RequestPostProcessor. For your example, you could do something like this:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// use this if CustomUser (principal) implements UserDetails
mvc
.perform(get("/").with(user(principal)))
...
// otherwise use this
mvc
.perform(get("/").with(authentication(auth)))
...
Using Annotations
You can also use annotations to specify the user. Since you use a custom user object (i.e. CurrentUser), you would probably consider using #WithUserDetails or #WithSecurityContext.
#WithUserDetails makes sense if you expose the UserDetailsService as a bean and you are alright with the user being looked up (i.e. it must exist). An example of #WithUserDetails might look like:
#Test
#WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
}
The alternative is to use #WithSecurityContext. This makes sense if you do not want to require the user to actually exist (as is necessary for WithUserDetails). I won't elaborate on this as it is well documented and without more details about your object model, I cannot provide a concrete example of this.