I have a UserControllerTest which is testing my UserConstroller. There is a test:
#Test
public void shouldRemoveUser() throws Exception {
mockMvc.perform(put("/user/register?email=test5#gmail.com&password=aaa"))
.andExpect(status().isOk());
mockMvc.perform(get("/user?email=test5#gmail.com"))
.andExpect(status().isOk());
mockMvc.perform(delete("/user?email=test5#gmail.com"))
.andExpect(status().isOk());
mockMvc.perform(get("/user?email=test5#gmail.com"))
.andExpect(status().isNotFound());
}
And the implementation which I want to test:
#RequestMapping(value = "register")
public ResponseEntity<?> createUser(
#RequestParam(value = "email") String email,
#RequestParam(value = "password", defaultValue = "") String password) {
MyUser myUser = new [...]
userRepository.save(myUser );
return ResponseEntity.ok(myUser);
}
#RequestMapping(method = RequestMethod.DELETE)
public ResponseEntity<?> delete(#RequestParam(value = "email") String email) {
MyUser userByEmail = userRepository.findMyUserByEmail(email);
if (userByEmail == null) {
LOG.debug("Cannot delete user with id: {}", email);
throw new UserNotFoundException("Cannot find user with email: " + email);
}
userRepository.delete(userByEmail);
return ResponseEntity.ok().build();
}
Turns out that the method delete works perfectly fine when fired via Postman or any other Rest Client. However, in tests the result is as follows:
java.lang.AssertionError: Status
Expected :404
Actual :200
It seams like the user is somehow not deleted when the code runs in tests.
In test I used H2, but when I tried to test it manually via Postman I used PostgreSQL.
Related
So, I have been testing a springboot MVC today, and when I run a test, I can not get the error to activate through using the test:
#Test
void post_A_User_passwords_do_notMatch() throws Exception {
User newUser = new User("TestName", "TestEmail#email.com", "NOT_THE_SAME","password123", true);
// * Building the request
RequestBuilder postAUser = MockMvcRequestBuilders.post("/users")
.contentType(MediaType.APPLICATION_JSON)
.header("authorization", "Bearer " + accessToken)
.content(objectMapper.writeValueAsString(newUser));
// * Sending the request
ResultActions posting = mockMvc.perform(postAUser);
// * Analysing the result
MvcResult mvcResult = posting
.andExpect(status().isBadRequest())
.andReturn();
}
The error never gets hit, even though I pass data through the mock post in JSON form. All I get is:
java.lang.AssertionError: Status expected:<400> but was:<200>
Expected :400
Actual :200
So, I changed the actual original entity so that the instance fields had matching passwords and then not matching passwords, and hey presto the error was hit when the entity has non-matching passwords in the instance fields. So, this led me to the conclusion that when the mapping is called, a blank user entity template is passed into the service (Well one that is the same as it is set in the user model), then it runs through the business service, then the JSON data is saved into the repo after this occurs, then a response is sent in JSON that can be accessed.
Ideally, I want to be able to hit the error through the JSON Data when, such that if two passwords do not match on the incoming data then the error is activated, through the user service.
So, I have questions, regarding the test, trying to hit the error and the output:
whether I have made the correct conclusion about the entity
getting passed in without the JSON data.
if any data gets saved in the as JAVA in JAVA end (IntelliJ) and it is not just persisted in the database and returned as a JSON response.
How would one hit the error?
The entity and then the relevant parts of the user controller and user service are posted below. I am just extending a CRUD repository for the user repo. I have set the spring security to permit all paths.
package bookapp.backend.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "users")
#Getter #Setter
public class User {
#Id // Change to UUID later after testing
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Integer id;
#Column
#JsonProperty("userName")
private String userName;
#Column
private String email;
#Column
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY, value = "password")
private String password = "";
#Transient
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Column
private String passwordConfirmation = "";
#Column
private Boolean isAdmin;
// Relationships
// reviews
#OneToMany
private List<Review>reviewList;
// Not sure how the relationship between myLibrary and myReadBooks with books will work
// Leaving this as it is for now, but might have to change annotations etc
#OneToMany
private List<Book> myLibrary;
#OneToMany
private List<Book> myReadBooks;
// * Constructors Added for Testing
public User() {
}
public User(String userName, String email, String password, String passwordConfirmation, Boolean isAdmin) {
this.userName = userName;
this.email = email;
this.password = password;
this.passwordConfirmation = passwordConfirmation;
this.isAdmin = isAdmin;
}
// * ToString Added for Testing
#Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
", passwordConfirmation='" + passwordConfirmation + '\'' +
", isAdmin=" + isAdmin +
'}';
}
}
This controller method in the controller
// ! Create a new user
#PostMapping("/users")
public User postUser(#RequestBody #Valid User user) { return userService.createUser(user); }
It also has this method in the service:
// ! This method creates a new user in the UserRepo
public User createUser(User user) {
// ! Check if password matches the passwordConfirmation
if (!user.getPassword().equals(user.getPasswordConfirmation())) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Password does not match password confirmation");
}
// ! Check if the user already exists in the UserRepo
// Optional<User> existingUser = userRepo.findByUsername(user.getUserName());
// if (existingUser.isPresent()) {
// throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "A user with that username already exists");
// }
// ! Encrypt password with bCrypt
String encodedPassword = bCryptPasswordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
return userRepo.save(user);
}
#Test
void post_A_User_ErrorCheck2() throws Exception {
objectMapper.disable(MapperFeature.USE_ANNOTATIONS);
User newUser = new User("testName", "TestEmail#email.com", "password$DO_NOT_MATCH123","password$123", true);
// * Building the request
RequestBuilder postAnUser = MockMvcRequestBuilders
.post("/users")
.contentType(MediaType.APPLICATION_JSON)
.header("authorization", "Bearer " + accessToken)
.content(objectMapper.writeValueAsString(newUser));
// * Performing the Request
ResultActions postingAnUser = mockMvc.perform(postAnUser);
// * Analysing the results
MvcResult testResult = postingAnUser
.andExpect(status().isNotFound())
.andReturn();
}
So, it turned out that the JSON ignore property #JsonProperty(access = JsonProperty.Access.WRITE_ONLY, value = "password") that annotates the entity prevents the serialization of the data. This occurs on the object mapper object that is present in the test. The #JsonProperty(access = JsonProperty.Access.WRITE_ONLY) is needed to prevent the passwords from getting returned in the response, but in this case, we use objectMapper.disable(MapperFeature.USE_ANNOTATIONS), to allow them to be written. Furthermore, the test in the #Test has an error isBadrequest() that does not match the HttpStatus.NOT_FOUND error in the user service.
I have a problem with my ControllerTest. I'm not sure how to test for the Optional - does someone know how? The other test gives me a NullPointerException for the stubbing : when(couponService.getCouponById(id)).thenReturn(expectedCoupon);
Would be awesome if someone could help me.
public class CouponControllerTest {
#MockBean
private CouponService couponService;
#MockBean
private UserService userService;
#Autowired
MockMvc mockMvc;
#Test
public void checkAndUpdateCoupon() throws Exception {
int id = 1;
int userId = 1;
Coupon expectedCoupon = new Coupon(1, 1);
when(couponService.getCouponById(id)).thenReturn(expectedCoupon);
List<User> userList = new ArrayList<User>();
when(userService.getAllUser()).thenReturn(userList);
List<Coupon> couponList = new ArrayList<Coupon>();
when(couponService.getAllCoupons()).thenReturn(couponList);
mockMvc.perform(get("/checkCoupon")
.param("id", "1")
.param("userId", "1"))
.andExpect(status().isOk())
.andExpect(view().name("couponPage"))
.andExpect(model().attribute("error", "Not correct user id or coupon id."))
.andExpect(model().attribute("users", userList))
.andExpect(model().attribute("coupons", couponList));
verify(couponService).updateCoupons(id, userId);
}
}
#Controller
public class CouponController {
#Autowired
CouponService couponService;
#Autowired
UserService userService;
#GetMapping("/checkCoupon")
public String checkCoupon(ModelMap model, #RequestParam Integer id, #RequestParam Integer userId, Coupon coupon) {
Optional<Coupon> couponFromDatabase = couponService.byUserIdAndId(coupon.getUserId(), coupon.getId());
if(couponFromDatabase.isEmpty()) {
String error = "Not correct user id or coupon id.";
model.addAttribute("error", error);
} else {
String message = couponService.updateCoupons(id, userId);
model.addAttribute("message", message);
}
List<User> userList = userService.getAllUser();
model.addAttribute("users", userList);
List<Coupon> couponList = couponService.getAllCoupons();
model.addAttribute("coupons", couponList);
return "couponPage";
}
}
I think you need to do some changes in mocking the first service.
when( couponService.byUserIdAndId(anyLong(), anyLong()) ).thenReturn( Optional.of(expectedCoupon) );
Here the anyLong() refer to any Incoming long data type number.
Override your existing code with this above line.
I have created an API using jersey and spring boot. Now when I hit POST request using Postman with following in request body:
{
"name":"something", "email":"something","location":"something","dateOfBirth":"something"
}
It works. Function to save this data is:
#POST
#Path("/addEmployee")
#Produces(MediaType.TEXT_PLAIN)
public String addEmployee(#RequestBody Employee employee) {
service.save(employee);
return "Saved Successfully";
}
Employee model is:
#Entity
#Table(name = "employee")
#XmlRootElement(name = "employee")
#EntityListeners(AuditingEntityListener.class)
public class Employee {
public Employee() {
}
#Id
#Column(nullable = false, name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, name = "name")
private String name;
#Column(nullable = false, name = "email")
private String email;
#Column(nullable = false, name = "location")
private String location;
#Column(nullable = false, name = "DOB")
private String dateOfBirth;
// getters and setters
This api is called by follwing function at client side:
#RequestMapping(value = "/addEmployee", method = RequestMethod.GET)
public ModelAndView addEmployee(ModelAndView model) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/api/addEmployee";
EmployeeInfo employee = new EmployeeInfo();
employee.setName("Ashish");
employee.setEmail("anyhing");
employee.setDateOfBirth("mybirthday");
employee.setLocation("home");
ResponseEntity<String> response = restTemplate.postForEntity(url, employee, String.class);
model.setViewName("homePage");
return model;
}
Employee info class is:
public class EmployeeInfo {
private String name;
private String email;
private String location;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
Error I'm getting is :
2018-09-16 15:57:13.706 ERROR 14892 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 404 null] with root cause
org.springframework.web.client.HttpClientErrorException: 404 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:86) ~[spring-web-4.3.19.RELEASE.jar:4.3.19.RELEASE]
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:708) ~[spring-web-4.3.19.RELEASE.jar:4.3.19.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:661) ~[spring-web-4.3.19.RELEASE.jar:4.3.19.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:621) ~[spring-web-4.3.19.RELEASE.jar:4.3.19.RELEASE]
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:415) ~[spring-web-4.3.19.RELEASE.jar:4.3.19.RELEASE]
at com.example.controller.Home.addEmployee(Home.java:82) ~[classes/:na]
and a long list like this.
Form which calls this is:
<form name="myform" method="post" action="addEmployee" >
<input type="submit" value="Save">
</form>
EDIT: On changing client side's method = RequestMethod.GET to RequestMethod.POST, nothing happens, still getting same erro
What I'm doing wrong?
After reviewing your code problem is at client side app where your back end is running on 8090 port while in api you calling is having 8080 for addEmployee.
Change this String url = "http://localhost:8080/api/addEmployee"; to String url = "http://localhost:8090/api/addEmployee"; and you should be good.
#RequestMapping(value = "/addEmployee", method = RequestMethod.GET)
public String addEmployee(ModelAndView model) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8090/api/addEmployee";
EmployeeInfo employee = new EmployeeInfo();
employee.setName("Ashish");
employee.setEmail("anyhing");
employee.setDateOfBirth("mybirthday");
employee.setLocation("home");
System.out.println("WE HAVE REACHED HERE");
String response = restTemplate.postForObject(url, employee, String.class);
System.out.println(response);
return "redirect:/home";
}
The 404 means that the requested source does not exist maby because one of these reasons:
There is no controller method to handle the POST request so try to change the which#RequestMapping(value = "/addEmployee", method = RequestMethod.GET) to #RequestMapping(value = "/addEmployee", method = RequestMethod.POST)
try to move this method and make it a service in a restful controller using this annotation #RestController
I see that you are accessing this /api/addEmployee which I think not configured right?
We should be using RequestMethod.POST instead of RequestMethod.GET
#RequestMapping(value = "/addEmployee", method = RequestMethod.POST)
public ModelAndView addEmployee(ModelAndView model) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/api/addEmployee";
EmployeeInfo employee = new EmployeeInfo();
employee.setName("Ashish");
employee.setEmail("anyhing");
employee.setDateOfBirth("mybirthday");
employee.setLocation("home");
ResponseEntity<String> response = restTemplate.postForEntity(url, employee, String.class);
model.setViewName("homePage");
return model;
}
I'm trying to validate an email that I receive from the post request body but it's doesn't work !
#RequestMapping(value = "myservice/emails", method = RequestMethod.POST)
public String requestFoo(#RequestBody #Email String email) {
return email;
}
When I send a request with a string that doesn't respect the email regex the function is executed and I receive a 200 status code.
Even do I add the #Valid annotation the result is always the same.
#RequestMapping(value = "myservice/emails", method = RequestMethod.POST)
public String testValidation(#Valid #RequestBody #Email String email) {
return email;
}
Start with Spring 3.2 #RequestBody method argument may be followed by Errors object, hence allowing handling of validation errors in the same #RequestMapping :
#RequestMapping(value = "myservice/emails", method = RequestMethod.POST)
public ResponseEntity<String> testValidation(#Valid #RequestBody #Email String email, Errors errors) {
if (errors.hasErrors()) {
return ResponseEntity.badRequest().body(ValidationErrorBuilder.fromBindingErrors(errors));
}
return email;
}
And create a custom validator :
public class ValidationErrorBuilder {
public static ValidationError fromBindingErrors(Errors errors) {
ValidationError error = new ValidationError("Validation failed. " + errors.getErrorCount() + " error(s)");
for (ObjectError objectError : errors.getAllErrors()) {
error.addValidationError(objectError.getDefaultMessage());
}
return error;
}
}
javax validation not working on method parameters.. This is a test code and none of javax validation works on method parameter...
#RequestMapping(value = "/{id}", method = RequestMethod.PUT, params = "action=testAction")
public Test update(
#Size(min = 1) #RequestBody List<String> ids,
#Min(3) #PathVariable String name) {
return doSomething(ids, name);
}
But i have class level validations which works perfectly...
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public RoleType create (#RequestBody #Validated(FieldType.class) User user) {
...
}
And
#Size(min = 2, max = 10, groups = { FieldType.class }, message = "Invalid user code")
public String getId() {
return _id ;
}
-- Solution --
all steps followed as per the accepted answer.
And another addition is annoation on class level
#Validated
class UserController
{
#RequestMapping(value = "/{id}", method = RequestMethod.PUT, params ="action=testAction")
public Test update(#Size(min = 1) #RequestBody List<String> ids,#Min(3) #PathVariable String name) {
return doSomething(ids, name);
}
}
you need to register MethodValidationPostProcessor bean to kick method level validation annotation
delegates to a JSR-303 provider for performing method-level
validation on annotated methods.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
then,
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public Test update(
#Size(min = 1) #RequestBody List<String> ids,
#Min(3) #PathVariable("id") String name) {
return doSomething(ids, name);
}
if you want to handle validation exception
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}