I'm new to Spring Boot and I'm trying to understand concept of testing.
Recently I was trying to implement DELETE request test for my controller, but I ran into some problems, and I have some quiestions:
Firstly, how can I test a Delete, PUT or GET single object request if there isn't any object to delete? Should I initiate some object at each start of the test?
Secondly, what's the point of injecting Service in such tests? We are mocking it, and that's all. I didn't find out by myself any usage of service in testing. Maybe there is a way to solve the first problem using it?
There is my testing class
package com.example.springrestuser;
import com.example.springrestuser.user.controller.UserController;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.security.Sha256;
import com.example.springrestuser.user.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.time.LocalDate;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#ExtendWith(SpringExtension.class)
#WebMvcTest(UserController.class)
public class UserControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private UserService userService;
#Test
public void canAddUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user#gmail.com")
.build();
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated());
}
#Test
public void canDeleteUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user#gmail.com")
.build();
userService.add(user); //maybe it would work somehow?
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
#Test
public void canPutUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user#gmail.com")
.build();
//sending the post method and then put?
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)));
user.setName("new_User");
mvc.perform(MockMvcRequestBuilders.put("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
#Test
public void canGetSingleUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user#gmail.com")
.build();
mvc.perform(MockMvcRequestBuilders.get("/api/users/{login}", "adam"))
.andExpect(status().isOk());
}
}
There is the controller class
package com.example.springrestuser.user.controller;
import com.example.springrestuser.user.dto.CreateUserRequest;
import com.example.springrestuser.user.dto.GetUserResponse;
import com.example.springrestuser.user.dto.GetUsersResponse;
import com.example.springrestuser.user.dto.UpdateUserRequest;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
#RestController
#RequestMapping("api/users")
public class UserController {
UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("")
public ResponseEntity<GetUsersResponse> getUsers() {
List<User> all = userService.findAll();
GetUsersResponse getUsersResponse = GetUsersResponse.entityToDtoMapper(all);
return ResponseEntity.ok(getUsersResponse);
}
#GetMapping("{login}")
public ResponseEntity<GetUserResponse> getUser(#PathVariable("login") String login) {
return userService.find(login)
.map(user -> ResponseEntity.ok(GetUserResponse.entityToDtoMapper(user)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
#PostMapping("")
public ResponseEntity<Void> createUser(#RequestBody CreateUserRequest request) {
User user = CreateUserRequest.dtoToEntityMapper(request);
userService.add(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}
#PutMapping("{login}")
public ResponseEntity<Void> updateUser(#RequestBody UpdateUserRequest request,
#PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
UpdateUserRequest.dtoToEntityMapper(request, user.get());
userService.update(user.get());
return ResponseEntity.accepted().build();
}
#DeleteMapping("{login}")
public ResponseEntity<Void> deleteUser(#PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
userService.delete(user.get());
return ResponseEntity.accepted().build();
}
}
Answering your questions:
With #WebMvcTest you are performing a test to your Controller layer without the rest of the application being loaded (usually called a Slice Test). This means that all you are going to test here is the logic in your Controller in addition to the serialization and deserialization of JSON. This means that you are not actually deleting anything because you are only testing your Controller layer. To actually test the deletion of something in your database you would need to do a fully-fledged Integration Test.
Without a mocked UserService bean, your UserController would be instantiated by Spring. What you should do in this #WebMvcTest is to mock UserService according to the logic you want to test in the Controller. As an example, in your #DeleteMapping("{login}") endpoint you would need to do the following tests:
Test #1: mock userService.find(login) to return an empty Optional<User> and then check that the HTTP Status Code is 404.
Test #2: mock userService.find(login) to return a non-empty Optional<User> and then check that the HTTP Status Code is 202.
Something along the following lines:
#ExtendWith(SpringExtension.class)
#WebMvcTest(UserController.class)
public class UserControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private UserService userService;
#Test
public void shouldDeleteUserIfFound() throws Exception {
// Arrange
String login = "user";
User user = User.builder()
.login(login)
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user#gmail.com")
.build();
doReturn(Optional.of(user)).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isAccepted());
}
#Test
public void shouldNotDeleteUserIfNotFound() throws Exception {
// Arrange
String login = "user";
doReturn(Optional.empty()).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isNotFound());
}
}
Related
Hi I'm trying to write Junit test for a controller but cant seem to find the proper approach can anyone please suggest how could write a junit test case for the following controller:
Contoller Class
public final class Contoller {
private static final Logger logger = LoggerFactory.getLogger(LiabilityContoller.class);
#Autowired
DemoService demoService;
#GetMapping("/report")
public ResponseEntity<String> getCollection(#RequestParam(name="cohort")String cohort){
List<Book> collection=demoService.getRecords(amount);
bookUtils.writeDataIntoCSVFile(collection, amount);
uploadReportsFilesToSftp(amount);
return new ResponseEntity<String>(" Files Are Generated", HttpStatus.OK);
}
Service class:
#Service
public class DemoService{
#Autowired
DemoRepository demoRepository;
public List<Liability> getRecords(String amount) {
List<Book> list=demoRepository.getRecordsByAmount(amount);
return list;
}
}
Repository
public interface DemoRepository extends JpaRepository<Book,Long>{
#Query(value="Select name,amount from Book where amount=:amount",
List<Book>getRecordsByAmount(String amount);
}
Utility:
public static void writeDataIntoReportsCSVFile(final List<Book> collection,final String amount,final String sftpLocalFile) {
try {
FileWriter FileWriter1 = new FileWriter(sftpLocalFile+"demo.csv");
CSVPrinter FilePrinter1 = new CSVPrinter(FileWriter1, CSVFormat.EXCEL);
printReportsHeader(FileWriter1);
for (Liability obj : collection) {
FilePrinter1.printRecord(obj.getName(),obj.getAmount());
}
FilePrinter1.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static void printReportsHeader(CSVPrinter FilePrinter1) {
try {
FilePrinter1.printRecord("NAME","AMOUNT");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
How could I test this controller what could be the best way to do so?
When you want to unit test a Spring application, the high level idea would be to write tests for the repository layer first, then write unit tests for the service, and then finally the controller layer.
Assuming that you have unit tests for DemoService, bookUtils, and uploadReportsFilesToSftp the Test for the controller would be exactly as below.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#ExtendWith(SpringExtension.class)
#WebMvcTest(Controller.class)
class ControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private DemoService jsonFileService;
#Test
void getCollection() throws Exception {
Mockito.doNothing().when(jsonFileService.getRecords(any()));
// similar stubbings here
this.mockMvc.perform(
get("/report")
.param("cohort", "testCohortValue")
).andExpect(
status().isOk()
);
}
}
I had problem in spring security when I try to register a new user and tested it in the postman it keep giving me a 401 unauthorized response.
I checked all the filters, control, service repository and everything I already checked all the issues here and even searched a lot about it in google but no answer I hope some one had the answer.
this is the code below:
this is the Security Configuration:
package app.gym.v1.Utility.Config;
import app.gym.v1.Utility.Filter.JwtAccessDeniedHandler;
import app.gym.v1.Utility.Filter.JwtAuthenticationEntryPoint;
import app.gym.v1.Utility.Filter.JwtAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import static app.gym.v1.Utility.Constant.SecurityConstant.*;
import static org.springframework.security.config.http.SessionCreationPolicy.*;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private JwtAuthorizationFilter jwtAuthorizationFilter;
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
public SecurityConfig(
JwtAuthorizationFilter jwtAuthorizationFilter,
JwtAccessDeniedHandler jwtAccessDeniedHandler,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
#Qualifier("userDetailsService")UserDetailsService userDetailsService,
BCryptPasswordEncoder bCryptPasswordEncoder) {
this.jwtAuthorizationFilter = jwtAuthorizationFilter;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and()
.sessionManagement().sessionCreationPolicy(STATELESS)
.and().authorizeRequests().antMatchers(PUBLIC_URLS).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManagerBean();
}
}
this is the Resource code:
package app.gym.v1.Resource;
import app.gym.v1.Model.User;
import app.gym.v1.Service.UserService;
import app.gym.v1.Utility.Exception.Domain.*;
import app.gym.v1.Utility.Exception.ExceptionHandling;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import static org.springframework.http.HttpStatus.OK;
#RestController
#RequestMapping(path = {"/","/user"})
public class UserControl extends ExceptionHandling {
private UserService userService;
#Autowired
public UserControl(UserService userService) {
this.userService = userService;
}
#PostMapping("/register")
public ResponseEntity<User> register(#RequestBody User user) throws UserNotFoundException, UsernameExistException, EmailExistException, IOException {
User newUser = userService.register(user.getUsername(), user.getEmail(), user.getPassword(), user.getRole());
return new ResponseEntity<>(newUser, OK);
}
}
this is the user implementation service:
package app.gym.v1.Utility.Impl;
import app.gym.v1.Model.User;
import app.gym.v1.Model.UserPrincipal;
import app.gym.v1.Repo.UserRepo;
import app.gym.v1.Service.UserService;
import app.gym.v1.Utility.Exception.Domain.*;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.transaction.Transactional;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import static app.gym.v1.Utility.Constant.UserImplConstant.*;
import static app.gym.v1.Utility.Enums.Role.*;
import static org.apache.commons.lang3.StringUtils.*;
#Service
#Transactional
#Qualifier("UserDetailsService")
public class UserServiceImpl implements UserService, UserDetailsService {
private Logger LOGGER = LoggerFactory.getLogger(getClass());
private UserRepo userRepo;
private BCryptPasswordEncoder passwordEncoder;
#Autowired
public UserServiceImpl(UserRepo userRepo, BCryptPasswordEncoder passwordEncoder) {
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findUserByUsername(username);
if (user == null) {
LOGGER.error("User with this phone number does not exist: " + username);
throw new UsernameNotFoundException("User with this phone number does not exist: " + username);
}else {
user.setLastLoginDateDisplay(user.getLastLoginDate());
user.setLastLoginDate(new Date());
userRepo.save(user);
UserPrincipal userPrincipal = new UserPrincipal(user);
LOGGER.info("Retrieving user with this phone number" + username);
return userPrincipal;
}
}
#Override
public User register(String username, String email, String password, String role) throws UserNotFoundException, UsernameExistException, EmailExistException {
validateNewUsernameAndEmail(EMPTY, username, email);
User user = new User();
user.setUserId(generateUserId());
user.setUsername(username);
user.setEmail(email);
user.setPassword(encodePassword(password));
user.setRole(USER.name());
user.setAuthorities(USER.getAuthorities());
user.setJoinDate(new Date());
user.setActive(true);
user.setNotLocked(true);
userRepo.save(user);
return user;
}
private String encodePassword(String password) {
return passwordEncoder.encode(password);
}
private String generateUserId() {
return RandomStringUtils.randomNumeric(20);
}
private String generatePassword() {
return RandomStringUtils.randomAlphanumeric(20);
}
private User validateNewUsernameAndEmail(String currentUsername, String newUsername, String newEmail) throws UserNotFoundException, UsernameExistException, EmailExistException {
User userByNewUsername = findUserByUsername(newUsername);
User userByNewEmail = findUserByEmail(newEmail);
if(isNotBlank(currentUsername)) {
User currentUser = findUserByUsername(currentUsername);
if(currentUser == null) {
throw new UserNotFoundException(NO_USER_FOUND_BY_USERNAME + currentUsername);
}
if(userByNewUsername != null && !currentUser.getId().equals(userByNewUsername.getId())) {
throw new UsernameExistException(USERNAME_ALREADY_EXISTS);
}
if(userByNewEmail != null && !currentUser.getId().equals(userByNewEmail.getId())) {
throw new EmailExistException(EMAIL_ALREADY_EXISTS);
}
return currentUser;
} else {
if(userByNewUsername != null) {
throw new UsernameExistException(USERNAME_ALREADY_EXISTS);
}
if(userByNewEmail != null) {
throw new EmailExistException(EMAIL_ALREADY_EXISTS);
}
return null;
}
}
}
the problem is with the registration my route is that(localhost:8080/user/register) or (localhost:8080/register).
I put a constant for them to make a public urls.
You need to annotate your SecurityConfig class with #Configuration or it won't be picked up.
If you do not have a custom security configuration set up properly, the application will use the default Spring Boot autoconfiguration which restricts access to all endpoints.
I cannot set authentication for my integration test of rest controller. Controller's method looks like this:
#RestController
#RequestMapping(BASE_URL)
#RequiredArgsConstructor
public class EventController {
public static final String BASE_URL = "/api/event";
#PostMapping
#PreAuthorize("hasRole('USER')")
public void createEvent() {
System.out.println("I am in controller");
}
}
and here is my test:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EventControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
void create() throws Exception {
this.mockMvc.perform(post(EventController.BASE_URL)
.with(authentication(new UsernamePasswordAuthenticationToken(
new MyPrincipal(100, "denis"),
null,
Collections.singletonList(new SimpleGrantedAuthority("USER"))
)))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"));
}
My test always failed due to status 401 so my mocked authentication doesn't work. Can you tell me how to fix it? Thank you in advice.
The easiest way to test secured requests is to use #WithMockUser(A_ROLE)
so your test could look like
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#SpringBootTest
#AutoConfigureMockMvc
class EventControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
#WithMockUser("USER")
void create() throws Exception {
this.mockMvc.perform(post(EventController.BASE_URL)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"));
}
}
Some remarks:
your test expects a result, so adopt your controller or test
#PostMapping
#PreAuthorize("hasRole('ROLE_USER')")
public ResponseEntity<String> createEvent() {
String result = "I am in controller";
System.out.println(result);
return ResponseEntity.ok().body(result);
}
you are doing/testing a POST so make sure that in your security configuration you do http.csrf().disable()....
or provide a csrf-token in your test
this.mockMvc.perform(post(EventController.BASE_URL)
.with(SecurityMockMvcRequestPostProcessors.csrf()) // provide a csrf-token
....
'
As part of TDD i want to be able to test every portion of my SpringBoot rest application. However i am unable to mock external calls.
Application structure
1. Few rest endpoints which internally call external rest endpoints.
2. All calls to external endpoints are orchestrated through a local http client which utilizes RestTemplate as httpClient.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.MOCK, classes = TestDrivenDevelopmentWithJavaApplication.class)
public class TestDrivenDevelopmentWithJavaApplicationTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private RestTemplate client;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
Structure1Root category = new Structure1Root();
Category cat = new Category();
cat.setCategoryName("Test1");
cat.setDescription("Test");
category.setD(cat);
Mockito.when(client.exchange(
ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
ArgumentMatchers.eq(Structure1Root.class)))
.thenReturn(new ResponseEntity<Structure1Root>(category, HttpStatus.OK));
}
#Test
public void testendpoint1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/endpoint1?token=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(content().string(org.hamcrest.Matchers.containsString("Test1")));
}
}
Even though i have setup the mock on client.exchange(RestTemplate.exchange), i see response returned by client.exchange is null and not the response specified in thenReturn
Controller Code
#RestController
#RequestMapping(path = Endpoint.base)
public class Endpoint {
public static final String base = "/api";
#Autowired
MyHttpClient<Structure2Root> client;
#Autowired
MyHttpClient<Structure1Root> Cclient;
#GetMapping(path = "/endpoint1")
public ResponseEntity<Structure2Root> callEndpt1(#RequestParam String token) {
Response<Structure2Root> resp = client
.execute("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json", Structure2Root.class);
return new ResponseEntity<Structure2Root>(resp.getResponse(), HttpStatus.OK);
}
#GetMapping(path = "/endpoint2")
public ResponseEntity<Structure1Root> callEndpt2(#RequestParam String token) {
Response<Structure1Root> resp = Cclient.execute(
"https://services.odata.org/V2/Northwind/Northwind.svc/Categories(1)?$format=json", Structure1Root.class);
return new ResponseEntity<Structure1Root>(resp.getResponse(),HttpStatus.OK);
}
}
And finally, local http client code
#Service
public class MyHttpClient<O> {
#Autowired
RestTemplate client;
public MyHttpClient() {
// TODO Auto-generated constructor stub
}
public Response<O> execute(String url, Class<O> generic) {
ResponseEntity<O> resp = client.exchange(url, HttpMethod.GET, null, generic);
return new Response<O>(resp.getStatusCode(), resp.getBody());
}
}
this client.execute is what i intend to intercept in the first code block
However never seems to work and always returns a null.
The Git Repo
Regards,
Veera
You have used the wrong object while mocking. You should be using Structure2Root rather then Structure1Root
The correct test class is below which is working perfectly fine.
package com.demo.samples.tdd;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.demo.samples.tdd.responses.Product;
import com.demo.samples.tdd.responses.Structure2Root;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import com.demo.samples.tdd.responses.Category;
import com.demo.samples.tdd.responses.Structure1Root;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.MOCK, classes = TestDrivenDevelopmentWithJavaApplication.class)
public class TestDrivenDevelopmentWithJavaApplicationTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private RestTemplate client;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
// Structure1Root category = new Structure1Root();
// Category cat = new Category();
// cat.setCategoryName("Test1");
// cat.setDescription("Test");
// category.setD(cat);
//
// Mockito.when(client.exchange(
// ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
// ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
// ArgumentMatchers.eq(Structure1Root.class)))
// .thenReturn(new ResponseEntity<Structure1Root>(category, HttpStatus.OK));
Structure2Root category2 = new Structure2Root();
Product product = new Product();
product.setProductName("Test1");
product.setUnitPrice("1");
category2.setD(product);
Mockito.when(client.exchange(
ArgumentMatchers.eq("https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)?$format=json"),
ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.eq(null),
ArgumentMatchers.eq(Structure2Root.class)))
.thenReturn(new ResponseEntity<Structure2Root>(category2, HttpStatus.OK));
}
#Test
public void testendpoint1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/endpoint1?token=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(content().string(org.hamcrest.Matchers.containsString("Test1")));
}
}
I seem to be missing something fundamental here:
#SpringBootApplication
public class Application {
User u = new User("USER", "PASSWORD",AuthorityUtils.createAuthorityList(
"ROLE_USER", "ROLE_ADMINISTRATOR"));
#Bean
public UserDetailsService userDetailsService() {
// returning a new User object works fine for every request
return username -> new User("USER", "PASSWORD",
AuthorityUtils.createAuthorityList(
"ROLE_USER", "ROLE_ADMINISTRATOR"));
// returning a previously created User object
// works only for the first request,
// subsequent requests get a 401 error
// return username -> u;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This Spring Boot (v1.5.1) application using the spring-boot-starter-security dependency knows of only one user as of now. Also, all of its endpoints should only be accessible to this very user. In all of the working examples I have seen, the UserDetailsService always returns a new object of type User, just like in the above example.
But when it returns a previously created object (like the object named u above), only the first request gets authenticated. Why ?
A good complete example, with JPA as well, can be found here
This is just an example. Password still needs to be encrypted/secured.
Application.java
package demo;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.repository.CrudRepository;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#ComponentScan
#EnableAutoConfiguration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class Application extends WebMvcConfigurerAdapter {
#Controller
protected static class HomeController {
#RequestMapping("/")
#Secured("ROLE_ADMIN")
public String home(Map<String, Object> model) {
model.put("message", "Hello World");
model.put("title", "Hello Home");
model.put("date", new Date());
return "home";
}
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/access").setViewName("access");
}
#Bean
public ApplicationSecurity applicationSecurity() {
return new ApplicationSecurity();
}
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
protected static class AuthenticationSecurity extends
GlobalAuthenticationConfigurerAdapter {
#Autowired
private Users users;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(users);
}
}
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().antMatchers("/login").permitAll().anyRequest()
.fullyAuthenticated().and().formLogin().loginPage("/login")
.failureUrl("/login?error").and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).and()
.exceptionHandling().accessDeniedPage("/access?error");
// #formatter:on
}
}
}
#Service
class Users implements UserDetailsService {
private UserRepository repo;
#Autowired
public Users(UserRepository repo) {
this.repo = repo;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = repo.findByName(username);
if (user == null) {
return null;
}
List<GrantedAuthority> auth = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
if (username.equals("admin")) {
auth = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
}
String password = user.getPassword();
return new org.springframework.security.core.userdetails.User(username, password,
auth);
}
}
#Repository
interface UserRepository extends CrudRepository<User, Long> {
User findByName(String name);
}
#Entity
class User {
#GeneratedValue
#Id
private Long id;
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
I believe it is due to the Spring User object purging the password after authentication
from -> https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/User.html
Note that this implementation is not immutable. It implements the CredentialsContainer interface, in order to allow the password to be erased after authentication. This may cause side-effects if you are storing instances in-memory and reusing them. If so, make sure you return a copy from your UserDetailsService each time it is invoked.