I've written a typical three layer Spring Boot REST API and am building out the tests for it. The API itself works fine but I am running into issues getting the controller tests to work. The body that's being returned is empty because the object the controller layer is getting back is null. Here are the main dependencies in play.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
I've mocked out the service layer but the when statement in the test doesn't seem to be firing as I'd expect.
Here's the test itself:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureMockMvc
public class VehicleControllerTest {
#MockBean
VehicleServiceImpl vService;
#Mock
HttpServletRequest mockRequest;
#Mock
Principal mockPrincipal;
#Autowired
MockMvc mockMvc;
Vehicle vehicle1;
#BeforeEach
public void setUp() throws ItemNotFoundException {
vehicle1 = new Vehicle();
vehicle1.setVin("5YJ3E1EA5KF328931");
vehicle1.setColor("black");
vehicle1.setDisplayName("Black Car");
vehicle1.setId(1L);
}
#Test
#WithMockUser("USER")
public void findVehicleByIdSuccess() throws Exception {
//Given **I think the problem is here***
when(vService.findVehicleById(any(),any(),any())).thenReturn(vehicle1);
//When
this.mockMvc.perform(get("/vehicles/1")).andDo(print())
//Then
.andExpect(status().isOk());
}
}
Here's the corresponding controller method:
#Secured("ROLE_USER")
public class VehicleController {
#JsonView(VehicleView.summary.class)
#GetMapping("/vehicles/{id}")
public Vehicle findVehicleById(#PathVariable Long id, Principal principal,
HttpServletRequest request) throws ItemNotFoundException {
log.info("In controller " +LogFormat.urlLogFormat(request,principal.getName()));
return vehicleService.findVehicleById(id,principal, request);
}
Here's the MockHTTPServletResponse. It has a status of 200 but the body is empty
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
For reference here's the service method that I'm trying to Mock
#Override
public Vehicle findVehicleById(Long id, Principal principal, HttpServletRequest request) throws ItemNotFoundException {
Optional<Vehicle> vehicle = vehicleRepository.findByIdAndUserId(id,principal.getName());
if (vehicle.isPresent()){
return vehicle.get();
} else {
throw new ItemNotFoundException(id,"vehicle");
}
}
I've tried different versions of Springboot but that hasn't helped. I had started off using 2.2.4 but I figured I would try the 2.1.X train since it has been around longer. I can confirm the correct method in the controller is being called because of the log output I'm getting.
Try replacing the following line:
when(vService.findVehicleById(any(),any(),any())).thenReturn(vehicle1);
with this (explicitity provide the class in any()):
when(vService.findVehicleById(any(Long.class),any(Principal.class),any(HttpServletRequest.class))).thenReturn(vehicle1);
Ignore the following part, as #M. Deinum pointed out that #MockBean does inject the mock object so this is irrelevant.
You did mock your service object but you didn't inject it into your controller.
#InjectMocks
private VehicleController vehicleController = new VehicleController();
Initialize these mock objects using MockitoAnnotations.initMocks(this) and instead of autowiring MockMvc object try to pass your controller in it like this:
#BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(vehicleController).build();
Related
I want to write unit tests for my spring controller. I'm using keycloak's openid flow to secure my endpoints.
In my tests I'm using the #WithMockUser annotation to mock an authenticated user. My problem is that I'm reading the userId from the token of the principal. My unit test now fails because the userId I read from the token is null;
if (principal instanceof KeycloakAuthenticationToken) {
KeycloakAuthenticationToken authenticationToken = (KeycloakAuthenticationToken) principal;
SimpleKeycloakAccount account = (SimpleKeycloakAccount) authenticationToken.getDetails();
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
AccessToken token = keycloakSecurityContext.getToken();
Map<String, Object> otherClaims = token.getOtherClaims();
userId = otherClaims.get("userId").toString();
}
Is there anything to easily mock the KeycloakAuthenticationToken?
#WithmockUser configures the security-context with a UsernamePasswordAuthenticationToken. This can be just fine for most use-cases but when your app relies on another Authentication implementation (like your code does), you have to build or mock an instance of the right type and put it in the test security-context: SecurityContextHolder.getContext().setAuthentication(authentication);
Of course, you'll soon want to automate this, building your own annotation or RequestPostProcessor
... or ...
take one "off the shelf", like in this lib of mine, which is available from maven-central:
<dependency>
<!-- just enough for #WithMockKeycloackAuth -->
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-addons</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<!-- required only for WebMvc "fluent" API -->
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
You can use it either with #WithMockKeycloackAuth annotations:
#RunWith(SpringRunner.class)
#WebMvcTest(GreetingController.class)
#ContextConfiguration(classes = GreetingApp.class)
#ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTests extends ServletUnitTestingSupport {
#MockBean
MessageService messageService;
#Test
#WithMockKeycloackAuth("TESTER")
public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretRouteIsNotAccessible() throws Exception {
mockMvc().get("/secured-route").andExpect(status().isForbidden());
}
#Test
#WithMockKeycloackAuth("AUTHORIZED_PERSONNEL")
public void whenUserIsGrantedWithAuthorizedPersonelThenSecretRouteIsAccessible() throws Exception {
mockMvc().get("/secured-route").andExpect(content().string(is("secret route")));
}
#Test
#WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
claims = #OpenIdClaims(
sub = "42",
email = "ch4mp#c4-soft.com",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy",
otherClaims = #Claims(stringClaims = #StringClaim(name = "foo", value = "bar"))))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
mockMvc().get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")));
}
Or MockMvc fluent API (RequestPostProcessor):
#RunWith(SpringRunner.class)
#WebMvcTest(GreetingController.class)
#ContextConfiguration(classes = GreetingApp.class)
#ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTest extends ServletKeycloakAuthUnitTestingSupport {
#MockBean
MessageService messageService;
#Test
public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretMethodIsNotAccessible() throws Exception {
mockMvc().with(authentication().roles("TESTER")).get("/secured-method").andExpect(status().isForbidden());
}
#Test
public void whenUserIsGrantedWithAuthorizedPersonelThenSecretMethodIsAccessible() throws Exception {
mockMvc().with(authentication().roles("AUTHORIZED_PERSONNEL")).get("/secured-method")
.andExpect(content().string(is("secret method")));
}
}
I don't like to add extra dependency, a specially when it related only for test case scenario. Also adding dependency in some project is a big process with security checks and needs to be approved by many Managers, seniors etc. So this is my solution that allows to mock Keycloak security context without instance of keycloak and other extra dependecies. This is copied form my project so adjustments are required. Hope it help.
#Test
void shouldFooOnProtectedEndpoint() throws Exception {
//given
AccessToken token = new AccessToken();
// by username i was differentiate is it allowed
token.setPreferredUsername(SUBMITTER_USERNAME);
KeycloakSecurityContext keycloakSecurityContext = mock(KeycloakSecurityContext.class);
given(keycloakSecurityContext.getToken()).willReturn(token);
KeycloakPrincipal principal = mock(KeycloakPrincipal.class);
given(principal.getKeycloakSecurityContext()).willReturn(keycloakSecurityContext);
Authentication auth = mock(Authentication.class);
given(auth.getPrincipal()).willReturn(principal);
SecurityContextHolder.getContext().setAuthentication(auth);
... test logic
}
I have spring controller handler. I have to test that handle by using Junit test case. I am new to Juint so not able to test Junit. I want to run this in eclipse.
spring handler:
#Controller
#RequestMapping("/core")
public class HelloController {
#RequestMapping(value = "/getEntityType", method = RequestMethod.GET)
public ResponseEntity<List<MyEnum >> getEntityType(HttpServletRequest
request, HttpServletResponse response) {
return new ResponseEntity<List<MyEnum >>(Arrays.asList(MyEnum.values()), HttpStatus.OK);
}
Enum Class:
public enum MyEnum {
FIRST, SECOND, THIRD;
}
TestCase:
#Test
public void testToFindEnumTypes() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "core/getEntityType");
MockHttpServletResponse response = new MockHttpServletResponse();
hello.Controller.getEntityType(request, response);
Assert.assertNotNull(getResponseJSON(response));
}
Please tell me how to Run Junit Test case for that handler. I am new to Junit testing.
From Eclipse, there should be a green run button that allows you to run JUnit tests.
Eclipse Help has this really good article explaining how to:
https://help.eclipse.org/neon/index.jsptopic=%2Forg.eclipse.jdt.doc.user%2FgettingStarted%2Fqs-junit.htm
Also, if running outside your Eclipse, and doing it in a maven build, you need to add junit dependency to your maven pom.xml file:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Then run maven test command as follows:
mvn clean test
Also, there a few syntax errors in your posted code. It should be like something like this:
public class HelloControllerTest {
private HelloController helloController = new HelloController();
#Test
public void testToFindEnumTypes() throws Exception {
// setup
MockHttpServletRequest request = new MockHttpServletRequest("GET", "core/getEntityType");
MockHttpServletResponse response = new MockHttpServletResponse();
// execution
ResponseEntity actualResponse = helloController.getEntityType(request, response);
// verify
assertNotNull(actualResponse);
assertEquals(HttpStatus.OK, actualResponse.getStatusCode());
List<MyEnum> myEnumList = (List<MyEnum>) actualResponse.getBody();
assertTrue(myEnumList.contains(MyEnum.FIRST));
assertTrue(myEnumList.contains(MyEnum.SECOND));
assertTrue(myEnumList.contains(MyEnum.THIRD));
}
Ideally, you should also verify correctly all the return values like i done in the example above.
I'm trying to test the home controller
#RequestMapping("/")
#ResponseBody
String home() {
return "Hello World!";
}
I'm using spring security using as username "user" and test as password by default but #PreAuthorize is not working
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#PreAuthorize("hasRole('ADMIN')")
public class HomeControllerTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
#WithMockUser(username = "user", password = "test", roles = "ADMIN")
public void home() throws Exception {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World!");
}
}
The result
Expected result:
<"[Hello World!]">
Actual result:
<"{"timestamp":1501100448216,"status":401,"error":"Unauthorized","message":"Full
authentication is required to access this resource","path":"/"}]">
Am I missing something?
Try to add the following to your test class:
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, listeners = {
WithSecurityContextTestExecutionListener.class
})
And the following dependency if you don't have it:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Spring security require an extra listener that is not present in tests by default so you need to tell spring to add it by specifing the #TestExecutionListeners annotation in merge mode so it will merge the current listed listeners with the listeners you want to add - in this case WithSecurityContextTestExecutionListener
I need to test some protected urls, therefore I need to set up a mock security context in my tests (junit).
In particular I need perform some gets and post against my web application, using an authenticated user.
Below there is my code, I am able to create a such security context but I need to inject it in the 'MockMvc' object.
I set the authentication object in the security context and it works, the output result of 'SecurityContextHolder.getContext().getAuthentication().getPrincipal()' is chanelle.evans#616747.com but when I call the GET on /profile I have an assertion error because I am redirected to my login page, and not to /profile.
#WebAppConfiguration
#ContextConfiguration(locations = {"classpath:spring/security.xml", "classpath:spring/view.xml"})
#ActiveProfiles("default")
#RunWith(SpringJUnit4ClassRunner.class)
public class AuthenticationTest {
#Autowired
WebApplicationContext ctx;
private MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#BeforeClass
public static void setUpBeforeClass() throws Exception {
}
#AfterClass
public static void tearDownAfterClass() throws Exception {
}
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(ctx).addFilters(springSecurityFilterChain).build();
//#formatter:off
UserDetailsLogic userDetailsLogic = null;
userDetailsLogic = ctx.getBean(UserDetailsLogic.class);
final UserDetailsImp userDetailsImp = new UserDetailsImp();
userDetailsImp.setAccountId(1001);
userDetailsImp.setUserId(8001);
userDetailsImp.setPassword("a378c92df7531df6fdf351f7ae1713f91f2dd2d45b9c6e1a8b02736ee3afec6595ff60465e9cb8da");
userDetailsImp.setUsername("chanelle.evans#616747.com");
userDetailsImp.setEmail("chanelle.evans#616747.com");
final Collection<GrantedAuthorityImplementation> authorities= new ArrayList<GrantedAuthorityImplementation>();
authorities.add(new GrantedAuthorityImplementation("ROLE_USER"));
userDetailsImp.setAuthorities(authorities);
userDetailsImp.setAccountNonExpired(true);
userDetailsImp.setAccountNonLocked(true);
userDetailsImp.setCredentialsNonExpired(true);
userDetailsImp.setEnabled(true);
final Authentication authToken = new UsernamePasswordAuthenticationToken (userDetailsImp.getUsername(), userDetailsImp.getPassword(), userDetailsImp.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
System.out.println("principal:"+SecurityContextHolder.getContext().getAuthentication().getPrincipal());
mockMvc.perform(get("/profile").principal(authToken)
.contentType(MediaType.TEXT_HTML)
.accept(MediaType.TEXT_HTML))
.andDo(print())
.andExpect(status().isOk())
.andExpect(redirectedUrl(null))
.andExpect(forwardedUrl(null));
//#formatter:on
}
I guess that I should put my authentication object inside the MockMvc object, but I do not know how
Does anyone have any idea?
This is something I wrote few days ago. I think that could be helpful (I tested the same thing against the login form, using the session for the second request) see loginUser1Ok(..))
See MvcTest.java in
m4nuv/easy-bank.
Add in pom.xml
<repository>
<id>spring-snaspho</id>
<url>http://repo.springsource.org/libs-milestone/</url>
</repository>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.0.M1</version>
</dependency>
and use org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors for authorization request. See the sample usage here, and Spring.io jira ticket SEC-2592.
A question about how to use test-mvc for unit testing.
I have a simple controller:
#Controller
#RequestMapping("/users")
public class UserController {
private UserService business;
#Autowired
public UserController(UserService bus)
{
business = bus;
}
#RequestMapping(value="{id}", method = RequestMethod.GET)
public #ResponseBody User getUserById(#PathVariable String id) throws ItemNotFoundException{
return business.GetUserById(id);
}
(( My idea is to keep the controllers so thin as possible.))
To test this controller I am trying to do something like this.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:mvc-dispatcher-servlet.xml"})
public class UserControllerTest extends ControllerTestBase {
UserService mockedService;
#Before
public void Setup()
{
MockitoAnnotations.initMocks( this );
mockedService = mock(UserService.class);
}
#Test
public void ReturnUserById() throws Exception{
User user = new User();
user.setName("Lasse");
stub(mockedService.GetUserById("lasse")).toReturn(user);
MockMvcBuilders.standaloneSetup(new UserController(mockedService)).build()
.perform(get("/users/lasse"))
.andExpect(status().isOk())
.andExpect(?????????????????????????????);
}
My intention is to check that proper json code is returned,,,,,,
I am not a pro,,, so I have not found a way to replace ??????????????????????? with code to do verify the returned string but I am certain that there must be a elegant way to do this
Can anyone fill me in?
//lg
content().string(containsString("some part of the string"))
assuming that you have this import:
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
Update: Adding jsonPath also based on your comments:
You can add a dependency to json-path, the 1.0.M1 seems to depend on an much older version of json-path though:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.5.5</version>
<scope>test</scope>
</dependency>
With this your test can look like this:
.andExpect(jsonPath("$.persons[0].first").value("firstName"));