How to create Unit Test for my Service Layer? - spring

I would like to understand and see the easiest way to create a Unit Test for my Service Layer. I've been researching for days on the easiest way to do this, but unfortunately I still haven't found a solution for my specific code. I tried to write the test many times myself, but people told me that the code could be done much better. Thank you in advance.
My Entity:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Builder
#Table
public class Appointment {
#Id
#SequenceGenerator(
name = "appointment_sequence",
sequenceName = "appointment_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "appointment_sequence"
)
private Long appointmentId;
#Column(
name = "date_of_appointment",
nullable = false
)
private LocalDate dateOfAppointment;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "patient_id",
referencedColumnName = "patientId"
)
private Patient patient;
public void connectWithPatient(Patient patient) {
this.patient = patient;
}
}
My Repository:
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
}
And my Service:
#Service
public class AppointmentService implements AppointmentServiceInterface {
private final AppointmentRepository appointmentRepository;
private final PatientRepository patientRepository;
#Autowired
public AppointmentService(AppointmentRepository appointmentRepository, PatientRepository patientRepository) {
this.appointmentRepository = appointmentRepository;
this.patientRepository = patientRepository;
}
#Override
public List<Appointment> getAllAppointments() {
return appointmentRepository.findAll();
}
#Override
public ResponseEntity<Appointment> getAppointmentById(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
return ResponseEntity.ok(appointment);
}
#Override
public Appointment createNewAppointment(Appointment appointment) {
return appointmentRepository.save(appointment);
}
#Override
public ResponseEntity<Appointment> updateAppointment(Long appointmentId, Appointment appointmentUpdatedDetails) {
Appointment updatedAppointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
updatedAppointment.setDateOfAppointment(appointmentUpdatedDetails.getDateOfAppointment());
appointmentRepository.save(updatedAppointment);
return ResponseEntity.ok(updatedAppointment);
}
#Override
public ResponseEntity<Appointment> deleteAppointment(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
appointmentRepository.delete(appointment);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
#Override
public Appointment makeAppointmentWithPatient(Long appointmentId, Long patientId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
Patient patient = patientRepository.findById(patientId)
.orElseThrow(()-> new ResourceNotFoundException("Patient with id " + patientId + " doesn't exist."));
appointment.connectWithPatient(patient);
return appointmentRepository.save(appointment);
}
}

Returning ResponseEntity is not something you would really like to do from the service layer - it's a place for the business logic and not for Web responses. So currently your service is bound to spring MVC.
On the positive side, it doesn't call the Database directly and instead has DAOs injected as dependencies.
So the real question is what would you like to test here? If you want to test the service itself, it can be unit-tested with a simple unit test, you don't need spring here:
Create mocks for the Repositories and inject them into the instance of the service which will be a "subject" of your test as if they're real DAOs.
Then specify expectations like "if someone calls findAll then return a list of entity objects. Again, there is no need to use any kind of database here
Verify the expected return result from the method of the service that you test with some assertion library
That's it.
Of course, if you'll return real objects rather than ResponseEntity from some methods your code will be way cleaner and hence the testing will be easier because you'll verify the real domain objects rather than web-mvc abstractions.

Related

Not persisting entity with thymeleaf, spting data and r2dbc

Im trying to create Skill entity through Thymeleaf form and post it:
controller's endpoint
#PostMapping("/add")
public String addSkill(#ModelAttribute Skill skill){
log.info( " orly? "+ (skill== null));
log.info(skill.getName() + " name was set");
log.info(skill.getId() + " i<--d");
log.info(skill.getLevel() + " level was set");
log.info(skill.getPriority() + " prior was set");
log.info(skill.getSkillGroupName() + " group id was set");
service.add(skill);
return TEMPLATE;
}
service's method
#Override
public Mono<Skill> add(Skill skill) {
log.debug("SKILL IS ______ "+ skill.getName() + " ____WAS SAVED");
return repository.save(skill);
}
repo
#Repository
public interface SkillRepository extends ReactiveCrudRepository<Skill, UUID> {
Mono<UUID> removeById(UUID id);
}
entity implements Persistable
#Data
#Table
#NoArgsConstructor
#AllArgsConstructor
public class Skill implements Persistable<UUID> {
#Id
private UUID id;
#Column("skill_name")
private String name;
private Level level;
private Priority priority;
#Column("skill_group_name")
private String skillGroupName;
#Override
public boolean isNew() {
boolean result = Objects.isNull(id);
this.id = result ? UUID.randomUUID() : this.id;
return result;
}
}
Main class is annotated with #EnableR2dbcRepositories.
When I submit form, I get the log, confirming that entity is not null, all fields but id aren't nulls. And that's all, service's method add(Skill skill) never produce logs, neither postgres shows tuple. Any ideas?

Bidirectional #OneToOne Spring Data JPA, Hibernate

I am using Bidirectional #OneToOne from Hibernate documentation. I have created an identical model for the test.
I can't get Phone via PhoneDetails. I get an error - Message Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy [com.example.model.Phone#1] - no Session.
I've tried many options and it doesn't work.
Please tell me how to get the Phone correctly? I sit all day trying to do this. I did not find any options on the Internet, so I ask here.
Phone.java
#Entity(name = "Phone")
public class Phone {
#Id
#GeneratedValue
private Long id;
#Column(name = "`number`")
private String number;
#OneToOne(mappedBy = "phone",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
private PhoneDetails details;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
// Getters and setters are omitted for brevity
public void addDetails(PhoneDetails details) {
details.setPhone( this );
this.details = details;
}
public void removeDetails() {
if ( details != null ) {
details.setPhone( null );
this.details = null;
}
}
}
PhoneDetails.java
#Entity(name = "PhoneDetails")
public class PhoneDetails {
#Id
#GeneratedValue
private Long id;
private String provider;
private String technology;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "phone_id")
private Phone phone;
public PhoneDetails() {
}
public PhoneDetails(String provider, String technology) {
this.provider = provider;
this.technology = technology;
}
// Getters and setters are omitted for brevity
}
LifecycleController.java
#Controller
public class LifecycleController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/savePhoneAndPhoneDetails")
public String savePersonAddress () {
Phone phone = new Phone( "123-456-7890" );
PhoneDetails details = new PhoneDetails( "T-Mobile", "GSM" );
phone.addDetails( details );
serviceJpa.savPhone( phone );
return "/savePhoneAndPhoneDetails";
}
#GetMapping(value = "/getPhone")
public String addPersonAddress () {
PhoneDetails address = serviceJpa.findPhoneDetailsById(2L).orElseThrow();
Phone phone = address.getPhone();
/*
An error appears here -
could not initialize proxy
[com.example.model.Phone#1] - no Session
*/
System.out.println(phone.getNumber());
return "/getPhone";
}
}
ServiceJpa.java
#Service
#Transactional
public class ServiceJpa {
#Autowired
PhoneJpa phoneJpa;
#Autowired
PhoneDetailsJpa phoneDetailsJpa;
#Transactional
public void savPhone(Phone phone) {
phoneJpa.save(phone);
}
#Transactional
public Optional<PhoneDetails> findPhoneDetailsById(Long id) {
return phoneDetailsJpa.findById(id);
}
}
interface PhoneJpa.java
#Repository
public interface PhoneJpa extends JpaRepository<Phone, Long> {
}
interface PhoneDetailsJpa.java
#Repository
public interface PhoneDetailsJpa extends JpaRepository<PhoneDetails, Long> {
}
I agree with Andriy's comment with a slight addition of "You should not access [lazily loaded] entity details outside transaction bounds". But, for starters, is there some reason you want the OneToOne to be FetchType.LAZY to begin with? If you changed it to EAGER, your "lazy" problem would be resolved by virtue of it no longer being a lazy reference but being a real hydrated object.
If that is not the exact route you want to take, there are a dozen ways to EAGERLY fetch things in general and frankly too many to present a single solution here as best/ideal. As your code exists, since all the dereferencing (for now) is happening inside your Controller, then Andriy's suggestion to add #Transaction to the Controller may suffice in that it will be lazily fetched when you need it.
But in the future, if you have Lazy elements in a POJO that get returned to the stack higher than the controller, say, just before they are serialized to JSON for example, then even the CONTROLLER's #Transactional wouldn't be "high" enough in the stack and you'll end up with the same Lazy init problem..
Also, by having it be Lazy and then dereferencing it elsewhere, you're guaranteeing two trips to the Database. With proper FETCH/JOIN eager loads, you would limit that to one, which can be another performance benefit.
So either way, you're back to the real problem at hand.. looking for ways to ensure your operations occur ENTIRELY inside a Transaction boundary OR having to completely hydrate the object so no "Lazy" danglers get dereferenced outside of that.. i.e. by making them eager or by force-initializing any potential Lazy proxies/collections.

JHipster - Insert in the database with the GET method

I have to create an application with Jhipster but i never use it before.
When a user send a GET request to the address http://localhost:8080/api/newmesure/{mac-address}/{value}
I want to insert a new mesure in my database.
First i created 3 entity "Plantes", "Capteurs" and "Mesures" with this format :
Image here : https://i.stack.imgur.com/zJqia.png (I'm not allowed to post)
I activated the JPA Filtering to create a #Query to insert data in my database but i read that was not possible.
In /src/main/java/com/mycompany/myapp/web/rest/MesuresRessources.java :
/**
* REST controller for managing {#link com.mycompany.myapp.domain.Mesures}.
*/
#RestController
#RequestMapping("/api")
public class MesuresResource {
private final Logger log = LoggerFactory.getLogger(MesuresResource.class);
private static final String ENTITY_NAME = "mesures";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final MesuresService mesuresService;
private final MesuresQueryService mesuresQueryService;
public MesuresResource(MesuresService mesuresService, MesuresQueryService mesuresQueryService) {
this.mesuresService = mesuresService;
this.mesuresQueryService = mesuresQueryService;
}
#GetMapping("/newMesure/{mac}/{value}")
public String newMesure(#PathVariable String mac,#PathVariable int value) {
log.debug("Adresse MAC : "+mac);
log.debug("Valeur : "+value);
#Query("SELECT valeur FROM Mesures WHERE id = 1") //not working
Mesures getValeur(); //not working
return "Mesure ajoutée";
}
}
In /src/main/java/com/mycompany/myapp/domain/Mesures.java :
/**
* A Mesures.
*/
#Entity
#Table(name = "mesures")
public class Mesures implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "valeur")
private Integer valeur;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties("macs")
private Capteurs mac;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getValeur() {
return valeur;
}
public Mesures valeur(Integer valeur) {
this.valeur = valeur;
return this;
}
public void setValeur(Integer valeur) {
this.valeur = valeur;
}
public Capteurs getMac() {
return mac;
}
public Mesures mac(Capteurs capteurs) {
this.mac = capteurs;
return this;
}
public void setMac(Capteurs capteurs) {
this.mac = capteurs;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Mesures)) {
return false;
}
return id != null && id.equals(((Mesures) o).id);
}
#Override
public int hashCode() {
return 31;
}
#Override
public String toString() {
return "Mesures{" +
"id=" + getId() +
", valeur=" + getValeur() +
"}";
}
}
Louan
Learning java with JHipster is probably not a wise idea, it uses a very rich technology stack which might lose you unless you invest enough time to learn the basics.
There are many things wrong in your code and approach:
You can't use #Query annotation inside the body of method a of your REST controller, it must be used in your #Repository interface, this code can't compile. See https://www.baeldung.com/spring-data-jpa-query for a quick introduction
JPA filtering is not related to inserting into database
In HTTP/REST, GET method is supposed to be idempotent. For making changes in your database you should use POST or PUT methods. See What is idempotency in HTTP methods?
Your entity naming convention is not consistent: use singular for entity classes because each entity object represents one single instance of Mesure. Here you have Plantes (plural), Capteur (singular) and Mesures (plural). For table names, JHipster uses singular but plural is quite common too because a table holds many rows. Of course, this is just a convention and you or your team may decide to apply another (like a prefix for table names) but the key point is to be consistent.

Updating entity with One to One relationship using Spring Data

I have an issue when updating entity with OneToOne relationship, it creates record instead of updating the existing one. Below are the sample entities.
#Entity
#Table(schema = "crm", name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
public Employee getEmployee() {
return employee;
}
}
#Entity
#Table(schema = "crm", name = "employees")
public class Employee {
#Id
public Long getId() {
return id;
}
#OneToOne
#MapsId //Use the person PK id value as Employee PK id
#JoinColumn(name = "id")
public Person getPerson() {
return person;
}
}
I am using the PagingAndSortingRepository of Spring Data. Below is the service layer to update the entity.
#Override
#Transactional
public EmployeeResponse updateEmployee(Employee aEmployee) {
EmployeeResponse response = new EmployeeResponse();
try {
Optional<Employee> probableEmployee = employeeRepository.findById(aEmployee.getId());
if (!probableEmployee.isPresent()) {
throw new RecordNotFoundException(String.format(MessageConstants.EMPLOYEE_ID_NOT_FOUND, aEmployee.getId()));
}
Employee existingEmployeeToUpdate = probableEmployee.get();
EmployeeEntityHelper.updateExistingEntity(aEmployee, existingEmployeeToUpdate);
existingEmployeeToUpdate = employeeRepository.save(existingEmployeeToUpdate);
response.setSuccessfulResponse(existingEmployeeToUpdate);
} catch (Exception ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
response.setErrorAttributes(false, ReturnCode.FAILED.getCode(), ex.getLocalizedMessage());
}
return response;
}
The EmployeeEntityHelper.updateExistingEntity(source, target) will simply copy all properties of entities from source to target.
The save() method will generate an insert for Person even if I am explicitly passing the id existing in DB. But for employee it will generate an update which is expected.
Below is the updateExistingEntity() method:
public static void updateExistingEntity(Employee source, Employee target) {
copyProperties(source, target, Arrays,asList("person", "employeeNumber", "hiredDate", "birthDate"));
}
private static void copyProperties(Object aSource, Object aTarget, Iterable<String> aProperties) {
BeanWrapper sourceWrapper = PropertyAccessorFactory.forBeanPropertyAccess(aSource);
BeanWrapper targetWrapper = PropertyAccessorFactory.forBeanPropertyAccess(aTarget);
aProperties.forEach(p ->
targetWrapper.setPropertyValue(p, sourceWrapper.getPropertyValue(p))
);
}
Generally, a new INSERT instead of UPDATEs could be due to:
Either entity passed to save has no ID set, thus persist is called under the hood
Or entity with given ID is not present in the database thus merge fails (and maybe it is persisted as a fallback, dunno)
Check if entities have set ID field.
In your model , override equal/hash-code and just chek the id parameter
maybe it will help you :)
The issue is already fixed, somehow the copyproperties logic is not right in the sense that I am updating/setting the person also.

#Transactional in Service

I have created a voting application and I have method that changes the number of votes. It implements an interface with #Transactional annotation.
#Transactional(readOnly = true)
public interface VotingService {
Vote getByRestaurantId(int restaurantId);
Vote get(int id);
List<Vote> getWithRestaurantsByDate(LocalDateTime date);
List<Vote> getWithRestaurantsToday(HttpServletResponse response, int id);
#Transactional
Vote voteFor(int restaurantId, int userId);
}
I use SpringBoot. Will it work correctly while simultaneously voting several users. And how can you test such an action?
The sequential voting is working properly.
Code for changes the number of voices like this:
#Service
public class VotingServiceImpl implements VotingService {
...
#Override
public Vote voteFor(int restaurantId, int userId) {
...
Vote vote = getByRestaurantId(restaurantId);
vote.setNumberOfVotes(vote.getNumberOfVotes() + 1)
...
return vote;
...
}
...
}
#Entity
#Table(name = "votes", uniqueConstraints = {#UniqueConstraint(columnNames = {"restaurant_id", "date", "votes"}, name = "votes_unique_restaurant_date_votes_idx")})
public class Vote extends AbstractEntity {
#Column(name = "votes")
private int numberOfVotes;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
public int getNumberOfVotes() {
return numberOfVotes;
}
public void setNumberOfVotes(int numberOfVotes) {
this.numberOfVotes = numberOfVotes;
}
public Vote() {
}
public Restaurant getRestaurant() {
return restaurant;
}
public void setRestaurant(Restaurant restaurant) {
this.restaurant = restaurant;
}
#Override
public String toString() {
return "Vote{" +
super.toString() +
"numberOfVotes=" + numberOfVotes +
", restaurant=" + restaurant +
'}';
}
}
Thanks!
VotingService is an interface.
The implementing class
VotingServiceImplis singleton class by default in spring. It is
shared between threads.
It should not have instance variable for
holding voting information.
You can verify the correctness of the service by executing parallel request using postman or jmeter

Resources